From c32722d24e78be0b23857f1741ddba0b12e34ecd Mon Sep 17 00:00:00 2001
From: jankapunkt
Date: Tue, 3 Sep 2024 11:42:18 +0200
Subject: [PATCH 1/4] tests(app): tests restored
---
app/.eslintrc.js | 13 +
.../async-storage.js | 4 +
app/__testHelpers__/createContextBaseTests.js | 52 +
app/__testHelpers__/expectThrowAsync.js | 16 +
app/__testHelpers__/getInvalidIntegers.js | 8 +
app/__testHelpers__/getInvalidNumbers.js | 13 +
app/__testHelpers__/mockCall.js | 13 +
app/__testHelpers__/mockCollection.js | 24 +
app/__testHelpers__/simpleRandom.js | 3 +
app/__testHelpers__/stub.js | 37 +
.../analytics/getDeviceData.tests.js | 5 +
.../components/ActionButton.tests.js | 5 +
app/__tests__/components/BackButton.tests.js | 5 +
app/__tests__/components/CatchErrors.tests.js | 5 +
.../components/CharacterInput.tests.js | 5 +
app/__tests__/components/Checkbox.tests.js | 5 +
app/__tests__/components/Confirm.tests.js | 5 +
app/__tests__/components/Connection.tests.js | 5 +
.../components/ErrorMessage.tests.js | 5 +
app/__tests__/components/FadePanel.tests.js | 5 +
app/__tests__/components/LeaButton.tests.js | 5 +
.../components/LeaButtonGroup.tests.js | 5 +
app/__tests__/components/LeaText.tests.js | 5 +
app/__tests__/components/Loading.tests.js | 5 +
.../components/MarkdownWithTts.tests.js | 5 +
.../components/NullComponent.tests.js | 5 +
.../components/ProfileButton.tests.js | 5 +
app/__tests__/components/RouteButton.tests.js | 5 +
app/__tests__/components/SoundIcon.tests.js | 5 +
.../components/ViewContainer.tests.js | 5 +
.../UnitContentElementFactory.tests.js | 53 +
.../createRoutableComponent.tests.js | 5 +
.../components/images/LeaLogo.tests.js | 5 +
app/__tests__/components/layout/Fill.tests.js | 5 +
.../components/progress/CurrentProgress.js | 5 +
.../components/progress/Diamond.tests.js | 5 +
.../progress/StaticCircularProgress.tests.js | 5 +
.../progress/computeProgress.tests.js | 28 +
.../progress/correctDiamondProgress.tests.js | 34 +
.../renderer/media/ImageRenderer.tests.js | 5 +
.../renderer/text/MarkdownRenderer.tests.js | 5 +
.../renderer/text/PlainTextRenderer.tests.js | 5 +
app/__tests__/components/tts.test.js | 359 +++
app/__tests__/contexts/Achievements.tests.js | 6 +
app/__tests__/contexts/Dimension.tests.js | 6 +
app/__tests__/contexts/Feedback.tests.js | 6 +
app/__tests__/contexts/Field.tests.js | 6 +
app/__tests__/contexts/Legal.tests.js | 6 +
app/__tests__/contexts/Level.tests.js | 6 +
app/__tests__/contexts/MapIcons.tests.js | 62 +
app/__tests__/contexts/Order.tests.js | 6 +
app/__tests__/contexts/Sync.tests.js | 228 ++
app/__tests__/contexts/UserProgress.tests.js | 197 ++
.../__snapshots__/MapIcons.tests.js.snap | 40 +
.../contexts/createContextStorage.tests.js | 59 +
app/__tests__/env/Config.tests.js | 5 +
app/__tests__/env/Sound.tests.js | 5 +
.../env/loadSettingsFromUserProfile.tests.js | 5 +
app/__tests__/errors/ErrorReporter.tests.js | 8 +
app/__tests__/errors/normalizeError.tests.js | 8 +
app/__tests__/hooks/useBackHandler.tests.js | 5 +
app/__tests__/hooks/useConnection.tests.js | 5 +
.../useKeyboardVisibilityHandler.tests.js | 5 +
app/__tests__/hooks/useLogin.tests.js | 5 +
app/__tests__/hooks/useProgress.tests.js | 5 +
.../hooks/useScreenIsActive.tests.js | 5 +
app/__tests__/hooks/useTimeout.tests.js | 5 +
app/__tests__/hooks/useUser.tests.js | 5 +
app/__tests__/hooks/useVoices.tests.js | 5 +
app/__tests__/i18n/i18n.test.js | 28 +
.../infrastructure/app/AppTemrinate.tests.js | 5 +
.../collections/collection.tests.js | 5 +
.../infrastructure/createRepository.tests.js | 17 +
.../factories/createCollection.tests.js | 5 +
app/__tests__/infrastructure/log/log.tests.js | 5 +
app/__tests__/items/choice/Choice.tests.js | 190 ++
.../items/choice/ChoiceRenderer.tests.js | 5 +
.../items/choice/ScoreChoice.tests.js | 5 +
.../choice/getChoiceEntryScoreColor.tests.js | 5 +
.../items/cloze/ClozeRenderer.tests.js | 5 +
.../items/cloze/Clozetokenizer.tests.js | 691 +++++
.../createScoringSummaryForInput.tests.js | 52 +
app/__tests__/items/cloze/scoreCloze.tests.js | 146 ++
app/__tests__/items/connect/Connect.tests.js | 5 +
.../connect/ConnectItemRenderer.tests.js | 5 +
.../items/connect/ScoreConnect.tests.js | 5 +
.../items/highlight/Highlight.tests.js | 5 +
.../highlight/HighlightRenderer.tests.js | 5 +
.../highlight/HighlightTokenizer.tests.js | 5 +
.../items/highlight/scoreHighlight.tests.js | 5 +
...getCompareValuesForSelectableItems.test.js | 22 +
.../items/utils/CompareState.tests.js | 41 +
.../items/utils/KeyboardTypes.tests.js | 13 +
app/__tests__/schema/schema.tests.js | 29 +
app/__tests__/schema/settingsSchema.tests.js | 10 +
.../schema/validateSettingsSchema.tests.js | 7 +
app/__tests__/scoring/Scoring.tests.js | 48 +
app/__tests__/scoring/getScoring.tests.js | 5 +
app/__tests__/screens/BaseScreen.tests.js | 5 +
.../screens/complete/Celebrate.tests.js | 5 +
.../screens/complete/CompleteScreen.tests.js | 5 +
.../complete/generateFeedback.tests.js | 48 +
.../complete/loadCompleteData.tests.js | 5 +
.../screens/home/HomeScreen.tests.js | 5 +
.../screens/home/laodHomeData.tests.js | 5 +
.../screens/map/loadMapData.tests.js | 261 ++
.../startup/createSessionValidator.tests.js | 5 +
app/__tests__/startup/initAppSession.tests.js | 5 +
app/__tests__/startup/initContexts.tests.js | 5 +
.../startup/initExceptionHandling.tests.js | 5 +
app/__tests__/startup/initSound.tests.js | 5 +
app/__tests__/startup/initTts.tests.js | 5 +
app/__tests__/state/AppSession.tests.js | 5 +
.../styles/createStyleSheet.tests.js | 5 +
app/__tests__/styles/makeTransparent.tests.js | 5 +
app/__tests__/styles/mergeStyles.tests.js | 5 +
app/__tests__/tts/TTSSpeedConfig.tests.js | 5 +
app/__tests__/tts/TTSVoiceConfig.tests.js | 5 +
app/__tests__/utils/array/byDocId.tests.js | 17 +
.../utils/array/byOrderedIds.tests.js | 33 +
.../utils/array/randomArrayElement.tests.js | 32 +
.../utils/array/toArrayIfNot.tests.js | 20 +
app/__tests__/utils/array/toDocId.tests.js | 8 +
.../utils/createTimesPromise.tests.js | 43 +
app/__tests__/utils/isOS.tests.js | 5 +
app/__tests__/utils/math/average.tests.js | 16 +
.../utils/math/getPositionOnCircle.tests.js | 21 +
.../utils/math/randomInclusive.tests.js | 14 +
.../utils/number/isSafeInteger.tests.js | 17 +
.../utils/number/isValidNumber.tests.js | 11 +
app/__tests__/utils/number/toInteger.tests.js | 16 +
.../utils/object/clearObject.tests.js | 17 +
.../utils/object/hasOwnProps.tests.js | 14 +
app/__tests__/utils/object/isDefined.tests.js | 10 +
.../utils/text/createSimpleTokenizer.tests.js | 12 +
app/__tests__/utils/text/isWord.tests.js | 10 +
app/babel.config.js | 8 +-
app/build.js | 14 +-
app/jest.config.js | 10 +
app/jestSetup.js | 3 +
app/lib/components/BackButton.js | 2 +-
app/lib/components/animated/DashedLine.js | 16 +-
.../renderer/media/ImageRenderer.js | 2 +-
app/lib/contexts/MapIcons.js | 15 +-
app/lib/hooks/useConnection.js | 2 +-
app/lib/hooks/useDevelopment.js | 2 +-
app/lib/i18n.js | 6 +-
.../cloze/createScoringSummaryForInput.js | 2 +-
app/lib/items/connect/ConnectItemRenderer.js | 1 -
app/lib/screens/auth/RegistrationScreen.js | 2 +-
app/lib/screens/map/components/Milestone.js | 2 +-
app/lib/screens/map/loadMapIcons.js | 20 -
app/lib/screens/map/loadProgressData.js | 1 -
.../renderer/InstructionsGraphicsRenderer.js | 4 +-
app/package-lock.json | 2270 ++++++++++++++++-
app/package.json | 14 +-
app/settings/settings.json | 16 +-
157 files changed, 5819 insertions(+), 188 deletions(-)
create mode 100644 app/.eslintrc.js
create mode 100644 app/__mocks__/@react-native-async-storage/async-storage.js
create mode 100644 app/__testHelpers__/createContextBaseTests.js
create mode 100644 app/__testHelpers__/expectThrowAsync.js
create mode 100644 app/__testHelpers__/getInvalidIntegers.js
create mode 100644 app/__testHelpers__/getInvalidNumbers.js
create mode 100644 app/__testHelpers__/mockCall.js
create mode 100644 app/__testHelpers__/mockCollection.js
create mode 100644 app/__testHelpers__/simpleRandom.js
create mode 100644 app/__testHelpers__/stub.js
create mode 100644 app/__tests__/analytics/getDeviceData.tests.js
create mode 100644 app/__tests__/components/ActionButton.tests.js
create mode 100644 app/__tests__/components/BackButton.tests.js
create mode 100644 app/__tests__/components/CatchErrors.tests.js
create mode 100644 app/__tests__/components/CharacterInput.tests.js
create mode 100644 app/__tests__/components/Checkbox.tests.js
create mode 100644 app/__tests__/components/Confirm.tests.js
create mode 100644 app/__tests__/components/Connection.tests.js
create mode 100644 app/__tests__/components/ErrorMessage.tests.js
create mode 100644 app/__tests__/components/FadePanel.tests.js
create mode 100644 app/__tests__/components/LeaButton.tests.js
create mode 100644 app/__tests__/components/LeaButtonGroup.tests.js
create mode 100644 app/__tests__/components/LeaText.tests.js
create mode 100644 app/__tests__/components/Loading.tests.js
create mode 100644 app/__tests__/components/MarkdownWithTts.tests.js
create mode 100644 app/__tests__/components/NullComponent.tests.js
create mode 100644 app/__tests__/components/ProfileButton.tests.js
create mode 100644 app/__tests__/components/RouteButton.tests.js
create mode 100644 app/__tests__/components/SoundIcon.tests.js
create mode 100644 app/__tests__/components/ViewContainer.tests.js
create mode 100644 app/__tests__/components/factories/UnitContentElementFactory.tests.js
create mode 100644 app/__tests__/components/factories/createRoutableComponent.tests.js
create mode 100644 app/__tests__/components/images/LeaLogo.tests.js
create mode 100644 app/__tests__/components/layout/Fill.tests.js
create mode 100644 app/__tests__/components/progress/CurrentProgress.js
create mode 100644 app/__tests__/components/progress/Diamond.tests.js
create mode 100644 app/__tests__/components/progress/StaticCircularProgress.tests.js
create mode 100644 app/__tests__/components/progress/computeProgress.tests.js
create mode 100644 app/__tests__/components/progress/correctDiamondProgress.tests.js
create mode 100644 app/__tests__/components/renderer/media/ImageRenderer.tests.js
create mode 100644 app/__tests__/components/renderer/text/MarkdownRenderer.tests.js
create mode 100644 app/__tests__/components/renderer/text/PlainTextRenderer.tests.js
create mode 100644 app/__tests__/components/tts.test.js
create mode 100644 app/__tests__/contexts/Achievements.tests.js
create mode 100644 app/__tests__/contexts/Dimension.tests.js
create mode 100644 app/__tests__/contexts/Feedback.tests.js
create mode 100644 app/__tests__/contexts/Field.tests.js
create mode 100644 app/__tests__/contexts/Legal.tests.js
create mode 100644 app/__tests__/contexts/Level.tests.js
create mode 100644 app/__tests__/contexts/MapIcons.tests.js
create mode 100644 app/__tests__/contexts/Order.tests.js
create mode 100644 app/__tests__/contexts/Sync.tests.js
create mode 100644 app/__tests__/contexts/UserProgress.tests.js
create mode 100644 app/__tests__/contexts/__snapshots__/MapIcons.tests.js.snap
create mode 100644 app/__tests__/contexts/createContextStorage.tests.js
create mode 100644 app/__tests__/env/Config.tests.js
create mode 100644 app/__tests__/env/Sound.tests.js
create mode 100644 app/__tests__/env/loadSettingsFromUserProfile.tests.js
create mode 100644 app/__tests__/errors/ErrorReporter.tests.js
create mode 100644 app/__tests__/errors/normalizeError.tests.js
create mode 100644 app/__tests__/hooks/useBackHandler.tests.js
create mode 100644 app/__tests__/hooks/useConnection.tests.js
create mode 100644 app/__tests__/hooks/useKeyboardVisibilityHandler.tests.js
create mode 100644 app/__tests__/hooks/useLogin.tests.js
create mode 100644 app/__tests__/hooks/useProgress.tests.js
create mode 100644 app/__tests__/hooks/useScreenIsActive.tests.js
create mode 100644 app/__tests__/hooks/useTimeout.tests.js
create mode 100644 app/__tests__/hooks/useUser.tests.js
create mode 100644 app/__tests__/hooks/useVoices.tests.js
create mode 100644 app/__tests__/i18n/i18n.test.js
create mode 100644 app/__tests__/infrastructure/app/AppTemrinate.tests.js
create mode 100644 app/__tests__/infrastructure/collections/collection.tests.js
create mode 100644 app/__tests__/infrastructure/createRepository.tests.js
create mode 100644 app/__tests__/infrastructure/factories/createCollection.tests.js
create mode 100644 app/__tests__/infrastructure/log/log.tests.js
create mode 100644 app/__tests__/items/choice/Choice.tests.js
create mode 100644 app/__tests__/items/choice/ChoiceRenderer.tests.js
create mode 100644 app/__tests__/items/choice/ScoreChoice.tests.js
create mode 100644 app/__tests__/items/choice/getChoiceEntryScoreColor.tests.js
create mode 100644 app/__tests__/items/cloze/ClozeRenderer.tests.js
create mode 100644 app/__tests__/items/cloze/Clozetokenizer.tests.js
create mode 100644 app/__tests__/items/cloze/createScoringSummaryForInput.tests.js
create mode 100644 app/__tests__/items/cloze/scoreCloze.tests.js
create mode 100644 app/__tests__/items/connect/Connect.tests.js
create mode 100644 app/__tests__/items/connect/ConnectItemRenderer.tests.js
create mode 100644 app/__tests__/items/connect/ScoreConnect.tests.js
create mode 100644 app/__tests__/items/highlight/Highlight.tests.js
create mode 100644 app/__tests__/items/highlight/HighlightRenderer.tests.js
create mode 100644 app/__tests__/items/highlight/HighlightTokenizer.tests.js
create mode 100644 app/__tests__/items/highlight/scoreHighlight.tests.js
create mode 100644 app/__tests__/items/shared/getCompareValuesForSelectableItems.test.js
create mode 100644 app/__tests__/items/utils/CompareState.tests.js
create mode 100644 app/__tests__/items/utils/KeyboardTypes.tests.js
create mode 100644 app/__tests__/schema/schema.tests.js
create mode 100644 app/__tests__/schema/settingsSchema.tests.js
create mode 100644 app/__tests__/schema/validateSettingsSchema.tests.js
create mode 100644 app/__tests__/scoring/Scoring.tests.js
create mode 100644 app/__tests__/scoring/getScoring.tests.js
create mode 100644 app/__tests__/screens/BaseScreen.tests.js
create mode 100644 app/__tests__/screens/complete/Celebrate.tests.js
create mode 100644 app/__tests__/screens/complete/CompleteScreen.tests.js
create mode 100644 app/__tests__/screens/complete/generateFeedback.tests.js
create mode 100644 app/__tests__/screens/complete/loadCompleteData.tests.js
create mode 100644 app/__tests__/screens/home/HomeScreen.tests.js
create mode 100644 app/__tests__/screens/home/laodHomeData.tests.js
create mode 100644 app/__tests__/screens/map/loadMapData.tests.js
create mode 100644 app/__tests__/startup/createSessionValidator.tests.js
create mode 100644 app/__tests__/startup/initAppSession.tests.js
create mode 100644 app/__tests__/startup/initContexts.tests.js
create mode 100644 app/__tests__/startup/initExceptionHandling.tests.js
create mode 100644 app/__tests__/startup/initSound.tests.js
create mode 100644 app/__tests__/startup/initTts.tests.js
create mode 100644 app/__tests__/state/AppSession.tests.js
create mode 100644 app/__tests__/styles/createStyleSheet.tests.js
create mode 100644 app/__tests__/styles/makeTransparent.tests.js
create mode 100644 app/__tests__/styles/mergeStyles.tests.js
create mode 100644 app/__tests__/tts/TTSSpeedConfig.tests.js
create mode 100644 app/__tests__/tts/TTSVoiceConfig.tests.js
create mode 100644 app/__tests__/utils/array/byDocId.tests.js
create mode 100644 app/__tests__/utils/array/byOrderedIds.tests.js
create mode 100644 app/__tests__/utils/array/randomArrayElement.tests.js
create mode 100644 app/__tests__/utils/array/toArrayIfNot.tests.js
create mode 100644 app/__tests__/utils/array/toDocId.tests.js
create mode 100644 app/__tests__/utils/createTimesPromise.tests.js
create mode 100644 app/__tests__/utils/isOS.tests.js
create mode 100644 app/__tests__/utils/math/average.tests.js
create mode 100644 app/__tests__/utils/math/getPositionOnCircle.tests.js
create mode 100644 app/__tests__/utils/math/randomInclusive.tests.js
create mode 100644 app/__tests__/utils/number/isSafeInteger.tests.js
create mode 100644 app/__tests__/utils/number/isValidNumber.tests.js
create mode 100644 app/__tests__/utils/number/toInteger.tests.js
create mode 100644 app/__tests__/utils/object/clearObject.tests.js
create mode 100644 app/__tests__/utils/object/hasOwnProps.tests.js
create mode 100644 app/__tests__/utils/object/isDefined.tests.js
create mode 100644 app/__tests__/utils/text/createSimpleTokenizer.tests.js
create mode 100644 app/__tests__/utils/text/isWord.tests.js
create mode 100644 app/jest.config.js
create mode 100644 app/jestSetup.js
delete mode 100644 app/lib/screens/map/loadMapIcons.js
diff --git a/app/.eslintrc.js b/app/.eslintrc.js
new file mode 100644
index 00000000..707fbe10
--- /dev/null
+++ b/app/.eslintrc.js
@@ -0,0 +1,13 @@
+// https://docs.expo.dev/guides/using-eslint/
+module.exports = {
+ extends: ['expo', 'standard'],
+ plugins: ['jest'],
+ env: {
+ 'jest/globals': true
+ },
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ 'react/display-name': 'off',
+ 'brace-style': ['error', 'stroustrup', { allowSingleLine: true }]
+ }
+}
diff --git a/app/__mocks__/@react-native-async-storage/async-storage.js b/app/__mocks__/@react-native-async-storage/async-storage.js
new file mode 100644
index 00000000..27868fbe
--- /dev/null
+++ b/app/__mocks__/@react-native-async-storage/async-storage.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line
+const storageMock = require('@react-native-async-storage/async-storage/jest/async-storage-mock')
+
+module.exports = storageMock
diff --git a/app/__testHelpers__/createContextBaseTests.js b/app/__testHelpers__/createContextBaseTests.js
new file mode 100644
index 00000000..1f91d3b7
--- /dev/null
+++ b/app/__testHelpers__/createContextBaseTests.js
@@ -0,0 +1,52 @@
+import { collectionExists, getCollection } from '../lib/infrastructure/collections/collections'
+import { createCollection } from '../lib/infrastructure/createCollection'
+import { simpleRandomHex } from '../lib/utils/simpleRandomHex'
+import AsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock'
+
+export const createContextBaseTests = ({ ctx, custom }) => {
+ const storageKey = `contexts-${ctx.name}`
+
+ beforeAll(() => {
+ jest.useFakeTimers({ advanceTimers: true })
+ })
+
+ if (ctx.collection) {
+ describe(ctx.collection.name, () => {
+ beforeAll(() => {
+ if (!collectionExists(ctx.name)) {
+ createCollection({
+ name: ctx.name,
+ isLocal: true
+ })
+ }
+ })
+
+ afterAll(() => {
+ const collection = getCollection(ctx.name)
+ ctx.collection = () => collection
+ })
+
+ it('throws if collection is not initiated', () => {
+ expect(() => ctx.collection())
+ .toThrow(`Collection ${ctx.name} not initialized`)
+ })
+ })
+ }
+
+ if (ctx.init) {
+ describe(ctx.init.name, function () {
+ it('loads sync docs into collection', async () => {
+ const _id = simpleRandomHex()
+ const doc = { _id, title: 'moo' }
+ await AsyncStorage.setItem(storageKey, JSON.stringify(doc))
+ await ctx.init()
+ const collection = getCollection(ctx.name)
+ expect(collection.findOne({ _id }))
+ .toStrictEqual({ _version: 1, ...doc })
+ })
+ })
+ }
+ if (custom) {
+ custom()
+ }
+}
diff --git a/app/__testHelpers__/expectThrowAsync.js b/app/__testHelpers__/expectThrowAsync.js
new file mode 100644
index 00000000..0676c783
--- /dev/null
+++ b/app/__testHelpers__/expectThrowAsync.js
@@ -0,0 +1,16 @@
+/**
+ * Test helper to test async functions to throw
+ * expected errors
+ * @param fn
+ * @param message
+ * @returns {Promise}
+ */
+export const expectThrowAsync = async ({ fn, message }) => {
+ try {
+ await fn()
+ throw new Error('Expected function to throw an Error')
+ }
+ catch (e) {
+ expect(e.message).toEqual(message)
+ }
+}
diff --git a/app/__testHelpers__/getInvalidIntegers.js b/app/__testHelpers__/getInvalidIntegers.js
new file mode 100644
index 00000000..b6ae009c
--- /dev/null
+++ b/app/__testHelpers__/getInvalidIntegers.js
@@ -0,0 +1,8 @@
+import { getInvalidNumbers } from './getInvalidNumbers'
+
+export const getInvalidIntegers = () => getInvalidNumbers().concat([
+ -1.1,
+ 0.2 + 0.1,
+ Number.MAX_SAFE_INTEGER + 1,
+ -Number.MAX_SAFE_INTEGER - 1
+])
diff --git a/app/__testHelpers__/getInvalidNumbers.js b/app/__testHelpers__/getInvalidNumbers.js
new file mode 100644
index 00000000..6052379d
--- /dev/null
+++ b/app/__testHelpers__/getInvalidNumbers.js
@@ -0,0 +1,13 @@
+export const getInvalidNumbers = () => [
+ null,
+ undefined,
+ '1',
+ Number.MAX_VALUE * 2,
+ -Number.MAX_VALUE * 2,
+ {},
+ NaN,
+ Infinity,
+ () => {},
+ true,
+ false
+]
diff --git a/app/__testHelpers__/mockCall.js b/app/__testHelpers__/mockCall.js
new file mode 100644
index 00000000..33054eff
--- /dev/null
+++ b/app/__testHelpers__/mockCall.js
@@ -0,0 +1,13 @@
+import Meteor from '@meteorrn/core'
+
+export const mockCall = callback => {
+ const data = {
+ waitDdpConnected: fn => fn()
+ }
+ jest
+ .spyOn(Meteor, 'getData')
+ .mockImplementation(() => data)
+ jest
+ .spyOn(Meteor, 'call')
+ .mockImplementation(callback)
+}
diff --git a/app/__testHelpers__/mockCollection.js b/app/__testHelpers__/mockCollection.js
new file mode 100644
index 00000000..a2dfa14c
--- /dev/null
+++ b/app/__testHelpers__/mockCollection.js
@@ -0,0 +1,24 @@
+import { createCollection } from '../lib/infrastructure/createCollection'
+
+const map = new Map()
+
+export const mockCollection = (context) => {
+ if (!map.has(context.name)) {
+ map.set(context.name, createCollection({
+ name: context.name,
+ isLocal: true
+ }))
+ }
+ const collection = map.get(context.name)
+ context.collection = () => collection
+ return context
+}
+
+export const resetCollection = context => context.collection().remove({})
+
+export const restoreCollection = context => {
+ map.delete(context.name)
+ context.collection = () => {
+ throw new Error('is not initialized')
+ }
+}
diff --git a/app/__testHelpers__/simpleRandom.js b/app/__testHelpers__/simpleRandom.js
new file mode 100644
index 00000000..5ac8bd44
--- /dev/null
+++ b/app/__testHelpers__/simpleRandom.js
@@ -0,0 +1,3 @@
+export const simpleRandom = (size = 6) => [...Array(size)]
+ .map(() => Math.floor(Math.random() * 16).toString(16))
+ .join('')
diff --git a/app/__testHelpers__/stub.js b/app/__testHelpers__/stub.js
new file mode 100644
index 00000000..6ea59246
--- /dev/null
+++ b/app/__testHelpers__/stub.js
@@ -0,0 +1,37 @@
+import sinon from 'sinon'
+
+const stubs = new Map()
+
+export const stub = (target, name, handler) => {
+ if (stubs.get(target)) {
+ throw new Error(`already stubbed: ${name}`)
+ }
+ const stubbedTarget = sinon.stub(target, name)
+ if (typeof handler === 'function') {
+ stubbedTarget.callsFake(handler)
+ }
+ else {
+ stubbedTarget.value(handler)
+ }
+ stubs.set(stubbedTarget, name)
+}
+
+export const restore = (target, name) => {
+ if (!target[name] || !target[name].restore) {
+ throw new Error(`not stubbed: ${name}`)
+ }
+ target[name].restore()
+ stubs.delete(target)
+}
+
+export const overrideStub = (target, name, handler) => {
+ restore(target, name)
+ stub(target, name, handler)
+}
+
+export const restoreAll = () => {
+ stubs.forEach((name, target) => {
+ stubs.delete(target)
+ target.restore()
+ })
+}
diff --git a/app/__tests__/analytics/getDeviceData.tests.js b/app/__tests__/analytics/getDeviceData.tests.js
new file mode 100644
index 00000000..91168081
--- /dev/null
+++ b/app/__tests__/analytics/getDeviceData.tests.js
@@ -0,0 +1,5 @@
+import { getDeviceData } from '../../lib/analystics/getDeviceData'
+
+describe(getDeviceData.name, function () {
+ test.todo('is not impl')
+})
diff --git a/app/__tests__/components/ActionButton.tests.js b/app/__tests__/components/ActionButton.tests.js
new file mode 100644
index 00000000..3a55918b
--- /dev/null
+++ b/app/__tests__/components/ActionButton.tests.js
@@ -0,0 +1,5 @@
+import { ActionButton } from '../../lib/components/ActionButton'
+
+describe(ActionButton.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/BackButton.tests.js b/app/__tests__/components/BackButton.tests.js
new file mode 100644
index 00000000..760d34db
--- /dev/null
+++ b/app/__tests__/components/BackButton.tests.js
@@ -0,0 +1,5 @@
+import { BackButton } from '../../lib/components/BackButton'
+
+describe(BackButton.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/CatchErrors.tests.js b/app/__tests__/components/CatchErrors.tests.js
new file mode 100644
index 00000000..2832510c
--- /dev/null
+++ b/app/__tests__/components/CatchErrors.tests.js
@@ -0,0 +1,5 @@
+import { CatchErrors } from '../../lib/components/CatchErrors'
+
+describe(CatchErrors.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/CharacterInput.tests.js b/app/__tests__/components/CharacterInput.tests.js
new file mode 100644
index 00000000..40609b40
--- /dev/null
+++ b/app/__tests__/components/CharacterInput.tests.js
@@ -0,0 +1,5 @@
+import { CharacterInput } from '../../lib/components/CharacterInput'
+
+describe(CharacterInput.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/Checkbox.tests.js b/app/__tests__/components/Checkbox.tests.js
new file mode 100644
index 00000000..505c24a6
--- /dev/null
+++ b/app/__tests__/components/Checkbox.tests.js
@@ -0,0 +1,5 @@
+import { Checkbox } from '../../lib/components/Checkbox'
+
+describe(Checkbox.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/Confirm.tests.js b/app/__tests__/components/Confirm.tests.js
new file mode 100644
index 00000000..1199c1de
--- /dev/null
+++ b/app/__tests__/components/Confirm.tests.js
@@ -0,0 +1,5 @@
+import { Confirm } from '../../lib/components/Confirm'
+
+describe(Confirm.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/Connection.tests.js b/app/__tests__/components/Connection.tests.js
new file mode 100644
index 00000000..6b62c4c0
--- /dev/null
+++ b/app/__tests__/components/Connection.tests.js
@@ -0,0 +1,5 @@
+import { Connecting } from '../../lib/components/Connecting'
+
+describe(Connecting.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/ErrorMessage.tests.js b/app/__tests__/components/ErrorMessage.tests.js
new file mode 100644
index 00000000..961da09d
--- /dev/null
+++ b/app/__tests__/components/ErrorMessage.tests.js
@@ -0,0 +1,5 @@
+import { ErrorMessage } from '../../lib/components/ErrorMessage'
+
+describe(ErrorMessage.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/FadePanel.tests.js b/app/__tests__/components/FadePanel.tests.js
new file mode 100644
index 00000000..921bb508
--- /dev/null
+++ b/app/__tests__/components/FadePanel.tests.js
@@ -0,0 +1,5 @@
+import { FadePanel } from '../../lib/components/FadePanel'
+
+describe(FadePanel.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/LeaButton.tests.js b/app/__tests__/components/LeaButton.tests.js
new file mode 100644
index 00000000..9629eeef
--- /dev/null
+++ b/app/__tests__/components/LeaButton.tests.js
@@ -0,0 +1,5 @@
+import { LeaButton } from '../../lib/components/LeaButton'
+
+describe(LeaButton.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/LeaButtonGroup.tests.js b/app/__tests__/components/LeaButtonGroup.tests.js
new file mode 100644
index 00000000..66b0176e
--- /dev/null
+++ b/app/__tests__/components/LeaButtonGroup.tests.js
@@ -0,0 +1,5 @@
+import { LeaButtonGroup } from '../../lib/components/LeaButtonGroup'
+
+describe(LeaButtonGroup.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/LeaText.tests.js b/app/__tests__/components/LeaText.tests.js
new file mode 100644
index 00000000..48df3ce4
--- /dev/null
+++ b/app/__tests__/components/LeaText.tests.js
@@ -0,0 +1,5 @@
+import { LeaText } from '../../lib/components/LeaText'
+
+describe(LeaText.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/Loading.tests.js b/app/__tests__/components/Loading.tests.js
new file mode 100644
index 00000000..be7f6809
--- /dev/null
+++ b/app/__tests__/components/Loading.tests.js
@@ -0,0 +1,5 @@
+import { Loading } from '../../lib/components/Loading'
+
+describe(Loading.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/MarkdownWithTts.tests.js b/app/__tests__/components/MarkdownWithTts.tests.js
new file mode 100644
index 00000000..9b1fca58
--- /dev/null
+++ b/app/__tests__/components/MarkdownWithTts.tests.js
@@ -0,0 +1,5 @@
+// import { Markdown } from '../../lib/components/MarkdownWithTTS'
+
+describe('MarkdownWithTTS', function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/NullComponent.tests.js b/app/__tests__/components/NullComponent.tests.js
new file mode 100644
index 00000000..eff8e6f6
--- /dev/null
+++ b/app/__tests__/components/NullComponent.tests.js
@@ -0,0 +1,5 @@
+import { NullComponent } from '../../lib/components/NullComponent'
+
+describe(NullComponent.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/ProfileButton.tests.js b/app/__tests__/components/ProfileButton.tests.js
new file mode 100644
index 00000000..7f3d9889
--- /dev/null
+++ b/app/__tests__/components/ProfileButton.tests.js
@@ -0,0 +1,5 @@
+import { ProfileButton } from '../../lib/components/ProfileButton'
+
+describe(ProfileButton.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/RouteButton.tests.js b/app/__tests__/components/RouteButton.tests.js
new file mode 100644
index 00000000..3eac6948
--- /dev/null
+++ b/app/__tests__/components/RouteButton.tests.js
@@ -0,0 +1,5 @@
+import { RouteButton } from '../../lib/components/RouteButton'
+
+describe(RouteButton.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/SoundIcon.tests.js b/app/__tests__/components/SoundIcon.tests.js
new file mode 100644
index 00000000..84eddb17
--- /dev/null
+++ b/app/__tests__/components/SoundIcon.tests.js
@@ -0,0 +1,5 @@
+import { SoundIcon } from '../../lib/components/SoundIcon'
+
+describe(SoundIcon.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/ViewContainer.tests.js b/app/__tests__/components/ViewContainer.tests.js
new file mode 100644
index 00000000..f41cefdd
--- /dev/null
+++ b/app/__tests__/components/ViewContainer.tests.js
@@ -0,0 +1,5 @@
+import { ViewContainer } from '../../lib/components/ViewContainer'
+
+describe(ViewContainer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/factories/UnitContentElementFactory.tests.js b/app/__tests__/components/factories/UnitContentElementFactory.tests.js
new file mode 100644
index 00000000..b281f266
--- /dev/null
+++ b/app/__tests__/components/factories/UnitContentElementFactory.tests.js
@@ -0,0 +1,53 @@
+import React from 'react'
+import { render } from '@testing-library/react-native'
+import { UnitContentElementFactory, useContentElementFactory } from '../../../lib/components/factories/UnitContentElementFactory'
+import { simpleRandom } from '../../../__testHelpers__/simpleRandom'
+import { Text } from 'react-native'
+
+describe('UnitContentElementFactory', () => {
+ afterEach(() => UnitContentElementFactory.flush())
+
+ describe(UnitContentElementFactory.register.name, () => {
+ it('registers a new renderer', () => {
+ const options = {
+ type: simpleRandom(),
+ subtype: simpleRandom(),
+ component: simpleRandom()
+ }
+ const unknown = {
+ type: simpleRandom(),
+ subtype: simpleRandom()
+ }
+ UnitContentElementFactory.register(options)
+ expect(UnitContentElementFactory.isRegistered(options)).toEqual(true)
+ expect(UnitContentElementFactory.isRegistered(unknown)).toEqual(false)
+ UnitContentElementFactory.flush()
+ expect(UnitContentElementFactory.isRegistered(options)).toEqual(false)
+ })
+ })
+ describe(UnitContentElementFactory.Renderer.name, () => {
+ it('renders by given keys', () => {
+ const props = { testID: simpleRandom() }
+ const text = simpleRandom()
+ const Component = () => ({text} )
+ const type = simpleRandom()
+ const subtype = simpleRandom()
+ const options = { type, subtype, component: Component }
+ UnitContentElementFactory.register(options)
+
+ const { Renderer } = useContentElementFactory()
+ const { getAllByText } = render( )
+ const elements = getAllByText(text)
+ expect(elements.length).toEqual(1)
+ })
+ it('renders a fallback by unknown keys', () => {
+ const type = simpleRandom()
+ const subtype = simpleRandom()
+ const text = `Fallback: ${type} / ${subtype}`
+ const { Renderer } = useContentElementFactory()
+ const { getAllByText } = render( )
+ const elements = getAllByText(text)
+ expect(elements.length).toEqual(1)
+ })
+ })
+})
diff --git a/app/__tests__/components/factories/createRoutableComponent.tests.js b/app/__tests__/components/factories/createRoutableComponent.tests.js
new file mode 100644
index 00000000..265b7ca2
--- /dev/null
+++ b/app/__tests__/components/factories/createRoutableComponent.tests.js
@@ -0,0 +1,5 @@
+import { createRoutableComponent } from '../../../lib/components/factories/createRoutableComponent'
+
+describe(createRoutableComponent.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/images/LeaLogo.tests.js b/app/__tests__/components/images/LeaLogo.tests.js
new file mode 100644
index 00000000..d64ba96c
--- /dev/null
+++ b/app/__tests__/components/images/LeaLogo.tests.js
@@ -0,0 +1,5 @@
+import { LeaLogo } from '../../../lib/components/images/LeaLogo'
+
+describe(LeaLogo.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/layout/Fill.tests.js b/app/__tests__/components/layout/Fill.tests.js
new file mode 100644
index 00000000..7a8bef86
--- /dev/null
+++ b/app/__tests__/components/layout/Fill.tests.js
@@ -0,0 +1,5 @@
+import { Fill } from '../../../lib/components/layout/Fill'
+
+describe(Fill.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/progress/CurrentProgress.js b/app/__tests__/components/progress/CurrentProgress.js
new file mode 100644
index 00000000..a925dda3
--- /dev/null
+++ b/app/__tests__/components/progress/CurrentProgress.js
@@ -0,0 +1,5 @@
+import { CurrentProgress } from '../../../lib/components/progress/CurrentProgress'
+
+describe(CurrentProgress.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/progress/Diamond.tests.js b/app/__tests__/components/progress/Diamond.tests.js
new file mode 100644
index 00000000..bff58938
--- /dev/null
+++ b/app/__tests__/components/progress/Diamond.tests.js
@@ -0,0 +1,5 @@
+import { Diamond } from '../../../lib/components/progress/Diamond'
+
+describe(Diamond.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/progress/StaticCircularProgress.tests.js b/app/__tests__/components/progress/StaticCircularProgress.tests.js
new file mode 100644
index 00000000..2e7cafbe
--- /dev/null
+++ b/app/__tests__/components/progress/StaticCircularProgress.tests.js
@@ -0,0 +1,5 @@
+import { StaticCircularProgress } from '../../../lib/components/progress/StaticCircularProgress'
+
+describe(StaticCircularProgress.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/progress/computeProgress.tests.js b/app/__tests__/components/progress/computeProgress.tests.js
new file mode 100644
index 00000000..f2a91310
--- /dev/null
+++ b/app/__tests__/components/progress/computeProgress.tests.js
@@ -0,0 +1,28 @@
+import { computeProgress } from '../../../lib/components/progress/computeProgress'
+
+describe(computeProgress.name, () => {
+ it('always returns a valid progress', () => {
+ [
+ { current: undefined, max: 9, expected: 0.1 },
+ { current: undefined, max: undefined, expected: 0.5 },
+ { current: 5, max: undefined, expected: 1 },
+ { current: -4, max: 9, expected: 0 },
+ { current: -1, max: 9, expected: 0 },
+ { current: 0, max: 9, expected: 0.1 },
+ { current: 1, max: 9, expected: 0.2 },
+ { current: 2, max: 9, expected: 0.3 },
+ { current: 5, max: 9, expected: 0.6 },
+ { current: 8, max: 9, expected: 0.9 },
+ { current: 9, max: 9, expected: 1 },
+ { current: 10, max: 9, expected: 1 },
+ { current: 1, max: -1, expected: 1 },
+ { current: 1, max: -2, expected: 0 },
+ { current: 1, max: Infinity, expected: 0 },
+ { current: Infinity, max: Infinity, expected: 0 },
+ { current: {}, max: {}, expected: 0 },
+ { current: 1, max: null, expected: 1 }
+ ].forEach(({ current, max, expected }) => {
+ expect(computeProgress({ current, max })).toEqual(expected)
+ })
+ })
+})
diff --git a/app/__tests__/components/progress/correctDiamondProgress.tests.js b/app/__tests__/components/progress/correctDiamondProgress.tests.js
new file mode 100644
index 00000000..4ee09d5d
--- /dev/null
+++ b/app/__tests__/components/progress/correctDiamondProgress.tests.js
@@ -0,0 +1,34 @@
+import { correctDiamondProgress } from '../../../lib/components/progress/correctDiamondProgress'
+
+describe(correctDiamondProgress.name, () => {
+ it('falls back to 0 if no valid number is given', () => {
+ ['', '1', null, undefined, NaN, Infinity, {}, [], true, false, () => {}]
+ .forEach(value => {
+ expect(correctDiamondProgress(value)).toEqual(0)
+ })
+ })
+ it('returns the appropriate value for a given number', () => {
+ [
+ { value: -2, expected: 0 },
+ { value: -1, expected: 0 },
+ { value: -0.1, expected: 0 },
+ { value: 0, expected: 0 },
+ { value: 0.01, expected: 0.3 },
+ { value: 0.1, expected: 0.3 },
+ { value: 0.24, expected: 0.3 },
+ { value: 0.25, expected: 0.3 },
+ { value: 0.31, expected: 0.31 },
+ { value: 0.5, expected: 0.5 },
+ { value: 0.74, expected: 0.74 },
+ { value: 0.75, expected: 0.75 },
+ { value: 0.76, expected: 0.75 },
+ { value: 0.89, expected: 0.75 },
+ { value: 0.9, expected: 1 },
+ { value: 0.99, expected: 1 },
+ { value: 1, expected: 1 },
+ { value: 1.01, expected: 1 }
+ ].forEach(({ value, expected }) => {
+ expect(correctDiamondProgress(value)).toEqual(expected)
+ })
+ })
+})
diff --git a/app/__tests__/components/renderer/media/ImageRenderer.tests.js b/app/__tests__/components/renderer/media/ImageRenderer.tests.js
new file mode 100644
index 00000000..7864c8fb
--- /dev/null
+++ b/app/__tests__/components/renderer/media/ImageRenderer.tests.js
@@ -0,0 +1,5 @@
+import { ImageRenderer } from '../../../../lib/components/renderer/media/ImageRenderer'
+
+describe(ImageRenderer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/renderer/text/MarkdownRenderer.tests.js b/app/__tests__/components/renderer/text/MarkdownRenderer.tests.js
new file mode 100644
index 00000000..0c67683e
--- /dev/null
+++ b/app/__tests__/components/renderer/text/MarkdownRenderer.tests.js
@@ -0,0 +1,5 @@
+import { MarkdownRenderer } from '../../../../lib/components/renderer/text/Markdown'
+
+describe(MarkdownRenderer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/renderer/text/PlainTextRenderer.tests.js b/app/__tests__/components/renderer/text/PlainTextRenderer.tests.js
new file mode 100644
index 00000000..48d8a543
--- /dev/null
+++ b/app/__tests__/components/renderer/text/PlainTextRenderer.tests.js
@@ -0,0 +1,5 @@
+import { PlainTextRenderer } from '../../../../lib/components/renderer/text/PlainTextRenderer'
+
+describe(PlainTextRenderer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/components/tts.test.js b/app/__tests__/components/tts.test.js
new file mode 100644
index 00000000..8f58f57c
--- /dev/null
+++ b/app/__tests__/components/tts.test.js
@@ -0,0 +1,359 @@
+import React from 'react'
+import { fireEvent, render, act } from '@testing-library/react-native'
+import { TTSengine, useTts } from '../../lib/components/Tts'
+import { Colors } from '../../lib/constants/Colors'
+
+const setSpeechOptions = { timeout: 25 }
+
+it('speaks a given text', async () => {
+ let speakCalled = false
+ let stopCalled = false
+
+ await TTSengine.setSpeech({
+ isSpeakingAsync: async function () {
+ return false
+ },
+ speak: (t) => {
+ expect(t).toBe('ttsMock.text')
+ speakCalled = true
+ },
+ stop: () => {
+ stopCalled = true
+ }
+ }, setSpeechOptions)
+
+ const { Tts } = useTts()
+ const { getByTestId } = render(
+ <>
+
+ >
+ )
+ const ttsBtn = getByTestId('ttsMock.text')
+ await act(() => fireEvent.press(ttsBtn))
+
+ expect(speakCalled).toBe(true)
+ expect(stopCalled).toBe(false)
+})
+
+it('updates global TTSEngine props as side effect', async function () {
+ TTSengine.setSpeech({
+ isSpeakingAsync: async function () {
+ return false
+ },
+ speak: async (t, options) => {
+ if (TTSengine.isSpeaking) { await act(() => options.onDone()) }
+ else { await act(() => options.onStart()) }
+ },
+ stop: () => {}
+ }, setSpeechOptions)
+
+ const { Tts } = useTts()
+ const { getByTestId } = render(
+ <>
+
+ >
+ )
+ const ttsBtn = getByTestId('ttsMock.text')
+ await act(() => fireEvent.press(ttsBtn))
+ expect(TTSengine.isSpeaking).toBe(true)
+ expect(TTSengine.speakId).toBe('ttsMock.text')
+ expect(TTSengine.iconColor).toBe(Colors.primary)
+
+ await act(() => fireEvent.press(ttsBtn))
+})
+
+it('stops if the action is executed before the tts is done', async function () {
+ let speakCalled = false
+ let stopCalled = false
+ let isSpeakingCalled = false
+ await TTSengine.setSpeech({
+ isSpeakingAsync: async function () {
+ if (isSpeakingCalled) {
+ return false
+ }
+
+ isSpeakingCalled = true
+ return true
+ },
+ speak: () => {
+ speakCalled = true
+ },
+ stop: () => {
+ stopCalled = true
+ }
+ }, setSpeechOptions)
+
+ const { Tts } = useTts()
+ const { getByTestId } = render(
+ <>
+
+ >
+ )
+ const ttsBtn = getByTestId('ttsMock.text')
+ await act(() => fireEvent.press(ttsBtn))
+
+ expect(speakCalled).toBe(true)
+ expect(stopCalled).toBe(true)
+ expect(TTSengine.isSpeaking).toBe(false)
+})
+
+it('resolve to a complete state via options.onStart', async () => {
+ let speakCalled = false
+ let stopCalled = false
+ let opts
+
+ await TTSengine.setSpeech({
+ isSpeakingAsync: async function () {
+ return false
+ },
+ speak: async (t, options) => {
+ opts = options
+ expect(t).toBe('ttsMock.text')
+ speakCalled = true
+
+ await act(() => options.onStart())
+ },
+ stop: () => {
+ stopCalled = true
+ }
+ }, setSpeechOptions)
+
+ const { Tts } = useTts()
+ const { getByTestId } = render(
+ <>
+
+ >
+ )
+ const ttsBtn = getByTestId('ttsMock.text')
+ await act(() => fireEvent.press(ttsBtn))
+
+ expect(speakCalled).toBe(true)
+ expect(stopCalled).toBe(false)
+
+ expect(TTSengine.isSpeaking).toBe(true)
+ expect(TTSengine.speakId).toBe('ttsMock.text')
+ expect(TTSengine.iconColor).toBe(Colors.primary)
+
+ // clean up
+ await act(() => opts.onDone())
+})
+
+it('resolve to a stopped state via options.onStopped', async () => {
+ let speakCalled = false
+ let stopCalled = false
+
+ await TTSengine.setSpeech({
+ isSpeakingAsync: async function () {
+ return false
+ },
+ speak: async (t, options) => {
+ expect(t).toBe('ttsMock.text')
+ speakCalled = true
+
+ await act(() => options.onStopped())
+ },
+ stop: () => {
+ stopCalled = true
+ }
+ }, setSpeechOptions)
+
+ const { Tts } = useTts()
+ const { getByTestId } = render(
+ <>
+
+ >
+ )
+ const ttsBtn = getByTestId('ttsMock.text')
+ await act(() => fireEvent.press(ttsBtn))
+ await act(() => TTSengine.stop())
+
+ expect(speakCalled).toBe(true)
+ expect(stopCalled).toBe(true)
+ expect(TTSengine.isSpeaking).toBe(false)
+ expect(TTSengine.speakId).toBe(0)
+ expect(TTSengine.iconColor).toBe(Colors.primary)
+})
+
+it('allows to attach beforeSpeak listener', async () => {
+ let handlerRun = false
+ let opts
+ const beforeSpeakHandler = () => {
+ handlerRun = true
+ }
+
+ TTSengine.on('beforeSpeak', beforeSpeakHandler)
+
+ let speakCalled = false
+ let stopCalled = false
+
+ await TTSengine.setSpeech({
+ isSpeakingAsync: async function () {
+ return false
+ },
+ speak: async (t, options) => {
+ opts = options
+ expect(t).toBe('ttsMock.text')
+ speakCalled = true
+
+ await act(() => options.onStart())
+ },
+ stop: () => {
+ stopCalled = true
+ }
+ }, setSpeechOptions)
+
+ const { Tts } = useTts()
+ const { getByTestId } = render(
+ <>
+
+ >
+ )
+ const ttsBtn = getByTestId('ttsMock.text')
+ await act(() => fireEvent.press(ttsBtn))
+
+ expect(handlerRun).toBe(true)
+ expect(speakCalled).toBe(true)
+ expect(stopCalled).toBe(false)
+ expect(TTSengine.off('beforeSpeak', () => {})).toBe(false)
+ expect(TTSengine.off('beforeSpeak', beforeSpeakHandler)).toBe(true)
+
+ // clean up
+ await act(() => opts.onDone())
+})
+
+it('resolve to a complete state via options.onDone', async () => {
+ let speakCalled = false
+ let stopCalled = false
+
+ await TTSengine.setSpeech({
+ isSpeakingAsync: async function () {
+ return false
+ },
+ speak: async (t, options) => {
+ expect(t).toBe('ttsMock.text')
+ speakCalled = true
+
+ await act(() => options.onDone())
+ },
+ stop: () => {
+ stopCalled = true
+ }
+ }, setSpeechOptions)
+
+ const { Tts } = useTts()
+ const { getByTestId } = render(
+ <>
+
+ >
+ )
+ const ttsBtn = getByTestId('ttsMock.text')
+ await act(() => fireEvent.press(ttsBtn))
+
+ expect(speakCalled).toBe(true)
+ expect(stopCalled).toBe(true)
+ expect(TTSengine.isSpeaking).toBe(false)
+ expect(TTSengine.speakId).toBe(0)
+ expect(TTSengine.iconColor).toBe(Colors.primary)
+})
+
+it('start 2 different tts processes successively', async () => {
+ let stopCalled = false
+ let tts1Speaking = false
+ let opts
+ await TTSengine.setSpeech({
+ isSpeakingAsync: async function () {
+ if (tts1Speaking) {
+ tts1Speaking = false
+ return true
+ }
+ return false
+ },
+ speak: async (t, options) => {
+ if (opts) {
+ // simulate stop / override
+ await act(() => opts.onDone())
+ }
+ opts = options
+ await act(() => options.onStart())
+ },
+ stop: () => {
+ stopCalled = true
+ }
+ }, setSpeechOptions)
+
+ const { Tts } = useTts()
+ const render1 = render(
+ <>
+
+ >
+ )
+
+ const render2 = render(
+ <>
+
+ >
+ )
+
+ const ttsBtn = render1.getByTestId('ttsMock.text1')
+ const ttsBtn2 = render2.getByTestId('ttsMock.text2')
+
+ await act(() => fireEvent.press(ttsBtn))
+ expect(TTSengine.isSpeaking).toBe(true)
+ expect(TTSengine.speakId).toBe('ttsMock.text1')
+ expect(TTSengine.iconColor).toBe(Colors.primary)
+ expect(stopCalled).toBe(false)
+ tts1Speaking = true
+ // clean up
+
+ await act(() => fireEvent.press(ttsBtn2))
+
+ expect(TTSengine.isSpeaking).toBe(true)
+ expect(TTSengine.speakId).toBe('ttsMock.text2')
+ expect(TTSengine.iconColor).toBe(Colors.primary)
+ expect(stopCalled).toBe(true)
+
+ // clean up
+ await act(() => opts.onDone())
+})
+
+describe('API', function () {
+ describe(TTSengine.component.name, function () {
+ it('returns the React component', () => {
+ const c = TTSengine.component()
+ expect(typeof c === 'function').toBe(true)
+ })
+ })
+ describe(TTSengine.updateSpeed.name, function () {
+ it('throws if speed is out of supported range', () => {
+ [-1, -0.1, 0, 0.09, 2.1, 3].forEach(value => {
+ expect(() => TTSengine.updateSpeed(value))
+ .toThrow(`New speed not in range, expected ${value} between 0.1 and 2.0`)
+ })
+ })
+ it('sets the new speed', () => {
+ for (let i = 0.1; i <= 2.0; i += 0.1) {
+ TTSengine.updateSpeed(i)
+ expect(TTSengine.currentSpeed).toBe(i)
+ }
+ TTSengine.currentSpeed = 1
+ })
+ })
+ describe(TTSengine.getVoices.name, function () {
+ it('returns voices, if they already exist', async () => {
+ TTSengine.availableVoices = [{ foo: 'bar' }]
+ const voices = await TTSengine.getVoices()
+ expect(voices).toEqual([{ foo: 'bar' }])
+ })
+ it('loads voices if not yet loaded', async () => {
+ TTSengine.availableVoices = null
+ const speechProvider = {
+ async getAvailableVoicesAsync () {
+ return [{ language: 'en-GB' }, { language: 'de-DE' }]
+ }
+ }
+ await TTSengine.setSpeech(speechProvider)
+ const voices = await TTSengine.getVoices()
+ expect(voices).toEqual([{ language: 'de-DE' }])
+ })
+ })
+})
diff --git a/app/__tests__/contexts/Achievements.tests.js b/app/__tests__/contexts/Achievements.tests.js
new file mode 100644
index 00000000..e97187a6
--- /dev/null
+++ b/app/__tests__/contexts/Achievements.tests.js
@@ -0,0 +1,6 @@
+import { Achievements } from '../../lib/contexts/Achievements'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+
+describe(Achievements.name, function () {
+ createContextBaseTests({ ctx: Achievements })
+})
diff --git a/app/__tests__/contexts/Dimension.tests.js b/app/__tests__/contexts/Dimension.tests.js
new file mode 100644
index 00000000..f1bc50ab
--- /dev/null
+++ b/app/__tests__/contexts/Dimension.tests.js
@@ -0,0 +1,6 @@
+import { Dimension } from '../../lib/contexts/Dimension'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+
+describe(Dimension.name, function () {
+ createContextBaseTests({ ctx: Dimension })
+})
diff --git a/app/__tests__/contexts/Feedback.tests.js b/app/__tests__/contexts/Feedback.tests.js
new file mode 100644
index 00000000..03111961
--- /dev/null
+++ b/app/__tests__/contexts/Feedback.tests.js
@@ -0,0 +1,6 @@
+import { Feedback } from '../../lib/contexts/Feedback'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+
+describe(Feedback.name, function () {
+ createContextBaseTests({ ctx: Feedback })
+})
diff --git a/app/__tests__/contexts/Field.tests.js b/app/__tests__/contexts/Field.tests.js
new file mode 100644
index 00000000..2dfc4e52
--- /dev/null
+++ b/app/__tests__/contexts/Field.tests.js
@@ -0,0 +1,6 @@
+import { Field } from '../../lib/contexts/Field'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+
+describe(Field.name, function () {
+ createContextBaseTests({ ctx: Field })
+})
diff --git a/app/__tests__/contexts/Legal.tests.js b/app/__tests__/contexts/Legal.tests.js
new file mode 100644
index 00000000..c08ba960
--- /dev/null
+++ b/app/__tests__/contexts/Legal.tests.js
@@ -0,0 +1,6 @@
+import { Legal } from '../../lib/contexts/Legal'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+
+describe(Legal.name, function () {
+ createContextBaseTests({ ctx: Legal })
+})
diff --git a/app/__tests__/contexts/Level.tests.js b/app/__tests__/contexts/Level.tests.js
new file mode 100644
index 00000000..d25c27a6
--- /dev/null
+++ b/app/__tests__/contexts/Level.tests.js
@@ -0,0 +1,6 @@
+import { Level } from '../../lib/contexts/Level'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+
+describe(Level.name, function () {
+ createContextBaseTests({ ctx: Level })
+})
diff --git a/app/__tests__/contexts/MapIcons.tests.js b/app/__tests__/contexts/MapIcons.tests.js
new file mode 100644
index 00000000..9d35f410
--- /dev/null
+++ b/app/__tests__/contexts/MapIcons.tests.js
@@ -0,0 +1,62 @@
+import { MapIcons } from '../../lib/contexts/MapIcons'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+import { simpleRandomHex } from '../../lib/utils/simpleRandomHex'
+import { render } from '@testing-library/react-native';
+
+describe(MapIcons.name, () => {
+ createContextBaseTests({ ctx: MapIcons })
+
+ describe(MapIcons.getIncrementalIconIndex.name, () => {
+ it('always returns -1 if no icons are registered', () => {
+ MapIcons.setField(simpleRandomHex())
+ expect(MapIcons.getIncrementalIconIndex()).toBe(-1)
+ })
+ it('returns a count that rotates around the icons set size', async () => {
+ const fieldId = simpleRandomHex()
+ await MapIcons.collection().insert({
+ fieldId,
+ icons: ['foo', 'bar', 'baz']
+ })
+
+ MapIcons.setField(fieldId)
+
+ const size = MapIcons.size()
+ expect(size).toBe(3)
+
+ let prev = -1
+ for (let i = 0; i < size; i++) {
+ const count = MapIcons.getIncrementalIconIndex()
+ expect(count).toBeGreaterThan(prev)
+ prev = count
+ }
+ // expect reset
+ expect(MapIcons.getIncrementalIconIndex()).toBe(0)
+ })
+ })
+ describe(MapIcons.render.name, () => {
+ it('returns null if the index is not within bounds', async () => {
+ const fieldId = simpleRandomHex()
+ const icons = ['edit', 'check', 'times']
+ await MapIcons.collection().insert({ fieldId, icons })
+ ;[-3, -2, -1, 3, 4, 5].forEach(index => {
+ expect(MapIcons.render(index))
+ .toBe(null)
+ })
+ })
+ it('renders an icon by given index', async () => {
+ const fieldId = simpleRandomHex()
+ const icons = ['edit', 'check', 'times']
+ await MapIcons.collection().insert({ fieldId, icons })
+
+ MapIcons.setField(fieldId)
+ const size = MapIcons.size()
+
+ for (let i = 0; i < size; i++) {
+ const MapIcon = MapIcons.render(i)
+ const component = render(MapIcon)
+ const tree = component.toJSON()
+ expect(tree).toMatchSnapshot()
+ }
+ })
+ })
+})
diff --git a/app/__tests__/contexts/Order.tests.js b/app/__tests__/contexts/Order.tests.js
new file mode 100644
index 00000000..c1939b71
--- /dev/null
+++ b/app/__tests__/contexts/Order.tests.js
@@ -0,0 +1,6 @@
+import { Order } from '../../lib/contexts/Order'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+
+describe(Order.name, function () {
+ createContextBaseTests({ ctx: Order })
+})
diff --git a/app/__tests__/contexts/Sync.tests.js b/app/__tests__/contexts/Sync.tests.js
new file mode 100644
index 00000000..a359e515
--- /dev/null
+++ b/app/__tests__/contexts/Sync.tests.js
@@ -0,0 +1,228 @@
+import Meteor from '@meteorrn/core'
+import { Sync } from '../../lib/infrastructure/sync/Sync'
+import { simpleRandom } from '../../__testHelpers__/simpleRandom'
+import { collectionExists } from '../../lib/infrastructure/collections/collections'
+import { restoreAll, stub, overrideStub } from '../../__testHelpers__/stub'
+import { createContextStorage } from '../../lib/contexts/createContextStorage'
+import { cleanup } from '@testing-library/react-native'
+import { ContextRepository } from '../../lib/infrastructure/ContextRepository'
+import { createCollection } from '../../lib/infrastructure/createCollection'
+import { mockCall } from '../../__testHelpers__/mockCall'
+const AsyncStorage = require('../../__mocks__/@react-native-async-storage/async-storage')
+
+describe(`${Sync.name}-system`, function () {
+ beforeAll(() => {
+ expect(() => Sync.collection())
+ .toThrow(`Collection ${Sync.name} not initialized`)
+ if (!collectionExists(Sync.name)) {
+ const collection = createCollection({
+ name: Sync.name,
+ isLocal: true
+ })
+ Sync.collection = () => collection
+ }
+ })
+
+ afterEach(() => {
+ restoreAll()
+ cleanup()
+ })
+
+ describe(Sync.init.name, function () {
+ it('throws on other methods if Sync is not initialized', async () => {
+ const expected = 'Sync.init must be called first'
+ await expect(Sync.isRequired()).rejects.toThrow(expected)
+ const onProgress = () => {}
+ await expect(Sync.run({ onProgress })).rejects.toThrow(expected)
+ })
+ it('ensures there is a Sync doc', async () => {
+ expect(Sync.collection().findOne()).toBe(undefined)
+ await AsyncStorage.getItem.mockReturnValueOnce(null)
+ await Sync.init()
+ const { _id, _version, ...doc } = Sync.collection().findOne()
+ expect(_id).toBeTruthy()
+ expect(doc).toEqual({})
+ Sync.collection().remove({})
+ })
+ it('loads the latest local sync doc from storage', async () => {
+ const doc = { _id: simpleRandom(), _version: 1, foo: 'bar' }
+ await AsyncStorage.getItem.mockReturnValueOnce(Meteor.EJSON.stringify(doc))
+ await Sync.init()
+ expect(Sync.collection().findOne()).toEqual(doc)
+ })
+ })
+
+ describe(Sync.isRequired.name, function () {
+ afterEach(() => {
+ Sync.reset()
+ })
+ it('returns false if no context is required to be synced', async () => {
+ const data = {}
+ mockCall((name, doc, callback) => callback(null, data))
+
+ const isRequired = await Sync.isRequired()
+ expect(Sync.getQueue()).toEqual([])
+ expect(isRequired).toBe(false)
+ })
+ it('returns true if a context is required to be synced', async () => {
+ const data = {
+ dimension: {
+ updatedAt: new Date(),
+ hash: simpleRandom()
+ },
+ foo: {
+ updatedAt: new Date(),
+ hash: simpleRandom()
+ }
+ }
+ mockCall((name, doc, callback) => callback(null, data))
+ stub(Sync.collection(), 'findOne', () => ({
+ dimension: {
+ updatedAt: new Date(),
+ hash: simpleRandom() // outdated
+ },
+ foo: {
+ updatedAt: data.foo.updatedAt,
+ hash: data.foo.hash
+ }
+ }))
+ const isRequired = await Sync.isRequired()
+ expect(Sync.getQueue()).toEqual([{
+ key: 'dimension',
+ hash: data.dimension.hash,
+ updatedAt: data.dimension.updatedAt
+ }])
+ expect(isRequired).toBe(true)
+ })
+ })
+
+ describe(Sync.syncContext.name, () => {
+ let name
+ let collection
+ let storage
+
+ beforeAll(() => {
+ name = simpleRandom()
+ collection = createCollection({ name, isLocal: true })
+ storage = createContextStorage({ name })
+ })
+
+ it('skips sync if no docs were returned from server', async () => {
+ mockCall((name, doc, callback) => callback(null, null))
+
+ expect(await Sync.syncContext({ name, collection, storage }))
+ .toBe(false)
+ })
+
+ it('inserts received docs from server', async () => {
+ const docs = [
+ { _id: simpleRandom(), foo: 'bar' },
+ { _id: simpleRandom(), bar: 'moo' }
+ ]
+
+ mockCall((name, doc, callback) => callback(null, docs))
+ stub(storage, 'saveFromCollection', () => {})
+
+ // existing docs are only updated
+ await collection.insert({ ...docs[0], foo: 'moo' })
+ await collection.insert({ _id: simpleRandom(), moo: 'milk' })
+
+ const synced = await Sync.syncContext({ name, collection, storage })
+ expect(synced).toBe(true)
+
+ expect(collection.find().count()).toBe(2)
+ expect(collection.findOne(docs[0]._id)).toStrictEqual({
+ ...docs[0],
+ _version: 2 // updated
+ })
+ expect(collection.findOne(docs[1]._id)).toStrictEqual({
+ ...docs[1],
+ _version: 1 // inserted
+ })
+ })
+ })
+
+ describe(Sync.run.name, function () {
+ it('throws if there is no sync required', async () => {
+ Sync.reset()
+ const onProgress = () => {}
+ await expect(Sync.run({ onProgress })).rejects.toThrow('Sync should not run if not required')
+ })
+ it('syncs the context and dispatches progress', async () => {
+ const name = simpleRandom()
+ const storage = createContextStorage({ name })
+ const targetCollection = createCollection({ name, isLocal: true })
+
+ Sync.collection().remove({})
+ Sync.collection().insert({})
+
+ const data = {
+ [name]: {
+ updatedAt: new Date(),
+ hash: simpleRandom()
+ }
+ }
+
+ mockCall((name, doc, callback) => callback(null, data))
+
+ const isRequired = await Sync.isRequired()
+ expect(Sync.getQueue()).toEqual([{
+ key: name,
+ hash: data[name].hash,
+ updatedAt: data[name].updatedAt
+ }])
+
+ expect(isRequired).toBe(true)
+
+ // run actual sync
+ const newDocs = [
+ { _id: simpleRandom(), _version: 1, foo: 'bar' },
+ { _id: simpleRandom(), _version: 1, bar: 'moo' }
+ ]
+
+ stub(storage, 'saveFromCollection', () => {})
+
+ // let backend return dimension docs
+ mockCall((name, doc, callback) => callback(null, newDocs))
+ let progress = 0
+
+ const onProgress = (data) => {
+ progress = data.progress
+ }
+
+ // should throw if no ctx found
+ await expect(() => Sync.run({ onProgress }))
+ .rejects.toThrow(`Expected ctx for key ${name}`)
+
+ // make ctx to be found
+ ContextRepository.add(name, { name, collection: () => targetCollection, storage })
+
+ const updateSync = await Sync.run({ onProgress })
+ expect(updateSync).toEqual({
+ [name]: {
+ hash: data[name].hash,
+ updatedAt: data[name].updatedAt
+ }
+ })
+
+ expect(await Sync.isRequired()).toBe(false)
+ expect(progress).toEqual(1)
+ expect(Sync.getQueue()).toEqual([])
+
+ // docs in collection
+ expect(targetCollection.find().count()).toBe(newDocs.length)
+ newDocs.forEach(doc => {
+ expect(targetCollection.findOne(doc._id)).toEqual(doc)
+ })
+
+ // sync updated
+ const { _id, _version, ...updatedSyncDoc } = Sync.collection().findOne()
+ expect(updatedSyncDoc).toEqual({
+ [name]: {
+ hash: data[name].hash,
+ updatedAt: data[name].updatedAt
+ }
+ })
+ })
+ })
+})
diff --git a/app/__tests__/contexts/UserProgress.tests.js b/app/__tests__/contexts/UserProgress.tests.js
new file mode 100644
index 00000000..78cd5ddc
--- /dev/null
+++ b/app/__tests__/contexts/UserProgress.tests.js
@@ -0,0 +1,197 @@
+import { UserProgress } from '../../lib/contexts/UserProgress'
+import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
+import { simpleRandomHex } from '../../lib/utils/simpleRandomHex'
+import { getCollection } from '../../lib/infrastructure/collections/collections'
+import Meteor from '@meteorrn/core'
+import { mockCall } from '../../__testHelpers__/mockCall'
+
+describe(UserProgress.name, function () {
+ createContextBaseTests({ ctx: UserProgress })
+
+ describe(UserProgress.update.name, () => {
+ it('skips if no progress doc is available', async () => {
+ const fieldId = simpleRandomHex()
+ const updated = await UserProgress.update({ fieldId })
+ expect(updated).toBe(false)
+ expect(getCollection(UserProgress.name)
+ .findOne({ fieldId }))
+ .toBe(undefined)
+ })
+ it('updates the progress doc', async () => {
+ const ProgressCollection = UserProgress.collection()
+ const fieldId = simpleRandomHex()
+ const unitSetDoc = {
+ _id: simpleRandomHex(),
+ dimensionId: simpleRandomHex(),
+ progress: 0,
+ competencies: 0
+ }
+ const userId = simpleRandomHex()
+ const docId = await ProgressCollection.insert({ userId, fieldId })
+ const beforeDoc = ProgressCollection.findOne(docId)
+ const updated = await UserProgress.update({ fieldId, unitSetDoc })
+ expect(updated).toBe(true)
+
+ const updatedDoc = ProgressCollection.findOne(docId)
+ expect(updatedDoc).not.toStrictEqual(beforeDoc)
+ expect(updatedDoc).toStrictEqual({
+ _id: docId,
+ fieldId,
+ _version: 2,
+ userId,
+ [unitSetDoc._id]: unitSetDoc
+ })
+ })
+ })
+ describe(UserProgress.fetchFromServer.name, function () {
+ it('returns undefined if no doc was loaded from server', async () => {
+ mockCall((name, args, callback) => {
+ callback(null, null)
+ })
+ const fieldId = simpleRandomHex()
+ const fetched = await UserProgress.fetchFromServer({ fieldId })
+ expect(fetched).toBe(undefined)
+ })
+ it('fetches a progress doc from the server and creates a new progress doc if not defined', async () => {
+ const dimensionId = simpleRandomHex()
+ const fieldId = simpleRandomHex()
+ const unitSet1 = {
+ _id: simpleRandomHex(),
+ dimensionId,
+ progress: 3,
+ competencies: 50,
+ complete: true
+ }
+ const unitSet2 = {
+ _id: simpleRandomHex(),
+ dimensionId,
+ progress: 16,
+ competencies: 207,
+ complete: false
+ }
+ const serverDoc = {
+ _id: simpleRandomHex(),
+ userId: simpleRandomHex(),
+ fieldId,
+ unitSets: [unitSet1, unitSet2]
+ }
+ jest
+ .spyOn(Meteor, 'call')
+ .mockImplementation((name, args, callback) => {
+ callback(null, serverDoc)
+ })
+
+ jest
+ .spyOn(Meteor, 'status')
+ .mockImplementation(() => {
+ return {
+ status: 'connected',
+ connected: true
+ }
+ })
+
+ const fetched = await UserProgress.fetchFromServer({ fieldId })
+ expect(fetched).toStrictEqual({
+ _id: serverDoc._id,
+ fieldId: serverDoc.fieldId,
+ userId: serverDoc.userId,
+ _version: 1,
+ unitSets: {
+ [unitSet1._id]: unitSet1,
+ [unitSet2._id]: unitSet2
+ }
+ })
+ })
+ it('fetches a progress doc from the server and updates an existing progress doc if defined', async () => {
+ const dimensionId = simpleRandomHex()
+ const fieldId = simpleRandomHex()
+ const unitSet1 = {
+ _id: simpleRandomHex(),
+ dimensionId,
+ progress: 3,
+ competencies: 50,
+ complete: true
+ }
+ const unitSet2 = {
+ _id: simpleRandomHex(),
+ dimensionId,
+ progress: 16,
+ competencies: 207,
+ complete: false
+ }
+ const serverDoc = {
+ _id: simpleRandomHex(),
+ userId: simpleRandomHex(),
+ fieldId,
+ unitSets: [unitSet1, unitSet2]
+ }
+ jest
+ .spyOn(Meteor, 'call')
+ .mockImplementation((name, args, callback) => {
+ callback(null, { ...serverDoc })
+ })
+
+ jest
+ .spyOn(Meteor, 'status')
+ .mockImplementation(() => {
+ return {
+ status: 'connected',
+ connected: true
+ }
+ })
+
+ await UserProgress.collection().insert({
+ _id: serverDoc._id,
+ fieldId: serverDoc.fieldId,
+ userId: serverDoc.userId,
+ unitSets: {}
+ })
+ const fetched = await UserProgress.fetchFromServer({ fieldId })
+ expect(fetched).toStrictEqual({
+ _id: serverDoc._id,
+ fieldId: serverDoc.fieldId,
+ userId: serverDoc.userId,
+ _version: 2,
+ unitSets: {
+ [unitSet1._id]: unitSet1,
+ [unitSet2._id]: unitSet2
+ }
+ })
+ })
+ })
+ describe(UserProgress.get.name, function () {
+ it('skips if no fieldId is given', async () => {
+ jest
+ .spyOn(UserProgress, 'fetchFromServer')
+ .mockImplementation(() => {
+ throw new Error('Unexpected call')
+ })
+ const args = [undefined, {}, { fieldId: undefined }, { fieldId: null }]
+ for (const options of args) {
+ const doc = await UserProgress.get(options)
+ expect(doc).toBe(undefined)
+ }
+ })
+ it('returns a document if it locally exists', async () => {
+ jest
+ .spyOn(UserProgress, 'fetchFromServer')
+ .mockImplementation(() => {
+ throw new Error('Unexpected call')
+ })
+ const fieldId = simpleRandomHex()
+ const docId = await UserProgress.collection().insert({ fieldId })
+ const expectedDoc = UserProgress.collection().findOne(docId)
+ const receivedDoc = await UserProgress.get({ fieldId })
+ expect(receivedDoc).toStrictEqual(expectedDoc)
+ })
+ it('fetches from server if doc does not exist', async () => {
+ const fieldId = simpleRandomHex()
+ const serverDoc = { _id: simpleRandomHex(), fieldId }
+ jest
+ .spyOn(UserProgress, 'fetchFromServer')
+ .mockImplementation(async () => serverDoc)
+ const receivedDoc = await UserProgress.get({ fieldId })
+ expect(receivedDoc).toStrictEqual(serverDoc)
+ })
+ })
+})
diff --git a/app/__tests__/contexts/__snapshots__/MapIcons.tests.js.snap b/app/__tests__/contexts/__snapshots__/MapIcons.tests.js.snap
new file mode 100644
index 00000000..3e0adbb3
--- /dev/null
+++ b/app/__tests__/contexts/__snapshots__/MapIcons.tests.js.snap
@@ -0,0 +1,40 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`mapIcons renders an icon by given index 1`] = `
+
+
+
+`;
+
+exports[`mapIcons renders an icon by given index 2`] = `
+
+
+
+`;
+
+exports[`mapIcons renders an icon by given index 3`] = `
+
+
+
+`;
diff --git a/app/__tests__/contexts/createContextStorage.tests.js b/app/__tests__/contexts/createContextStorage.tests.js
new file mode 100644
index 00000000..456b215c
--- /dev/null
+++ b/app/__tests__/contexts/createContextStorage.tests.js
@@ -0,0 +1,59 @@
+import { createContextStorage } from '../../lib/contexts/createContextStorage'
+import { simpleRandom } from '../../__testHelpers__/simpleRandom'
+import Meteor from '@meteorrn/core'
+import { addCollection } from '../../lib/infrastructure/collections/collections'
+const AsyncStorage = require('../../__mocks__/@react-native-async-storage/async-storage')
+
+const createCtx = () => {
+ const name = simpleRandom()
+ const c = new Meteor.Collection(null)
+ addCollection(name, c)
+ const collection = () => c
+ return { name, collection }
+}
+
+const createData = () => [
+ { _id: simpleRandom(), _version: 1, foo: 'bar' },
+ { _id: simpleRandom(), _version: 1, bar: 'moo' }
+].sort()
+
+describe(createContextStorage.name, function () {
+ beforeAll(() => {
+ jest.useFakeTimers({ advanceTimers: true })
+ })
+ it('creates a new storage instance', () => {
+ const ctx = createCtx()
+ const storage = createContextStorage(ctx)
+ expect(storage.name).toBe(ctx.name)
+ })
+
+ it('loads data from storage into collection', async () => {
+ const data = createData()
+ AsyncStorage.getItem.mockReturnValueOnce(Meteor.EJSON.stringify(data))
+ const ctx = createCtx()
+ const storage = createContextStorage(ctx)
+ await storage.loadIntoCollection()
+ const docs = ctx.collection().find().fetch()
+ expect(docs.length).toEqual(data.length)
+
+ data.forEach(doc => {
+ expect(ctx.collection().findOne(doc._id)).toEqual(doc)
+ })
+ })
+
+ it('saves data from collection to storage', async () => {
+ const data = createData()
+ const ctx = createCtx()
+ data.forEach(doc => ctx.collection().insert(doc))
+ const storage = createContextStorage(ctx)
+ await storage.saveFromCollection()
+
+ const loaded = await AsyncStorage.getItem(storage.key)
+ const docs = Meteor.EJSON.parse(loaded)
+ expect(docs.length).toEqual(data.length)
+ docs.forEach(doc => {
+ const found = data.find(element => element._id === doc._id)
+ expect(found).toEqual(doc)
+ })
+ })
+})
diff --git a/app/__tests__/env/Config.tests.js b/app/__tests__/env/Config.tests.js
new file mode 100644
index 00000000..d06191e8
--- /dev/null
+++ b/app/__tests__/env/Config.tests.js
@@ -0,0 +1,5 @@
+import { Config } from '../../lib/env/Config'
+
+describe(Config.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/env/Sound.tests.js b/app/__tests__/env/Sound.tests.js
new file mode 100644
index 00000000..287a7deb
--- /dev/null
+++ b/app/__tests__/env/Sound.tests.js
@@ -0,0 +1,5 @@
+import { Sound } from '../../lib/env/Sound'
+
+describe(Sound.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/env/loadSettingsFromUserProfile.tests.js b/app/__tests__/env/loadSettingsFromUserProfile.tests.js
new file mode 100644
index 00000000..bc509995
--- /dev/null
+++ b/app/__tests__/env/loadSettingsFromUserProfile.tests.js
@@ -0,0 +1,5 @@
+import { loadSettingsFromUserProfile } from '../../lib/env/loadSettingsFromUserProfile'
+
+describe(loadSettingsFromUserProfile.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/errors/ErrorReporter.tests.js b/app/__tests__/errors/ErrorReporter.tests.js
new file mode 100644
index 00000000..8421676c
--- /dev/null
+++ b/app/__tests__/errors/ErrorReporter.tests.js
@@ -0,0 +1,8 @@
+import { ErrorReporter } from '../../lib/errors/ErrorReporter'
+// import { MeteorError } from '../../lib/errors/MeteorError'
+// mport { AuthenticationError } from '../../lib/errors/AuthenticationError'
+// import { ConnectionError } from '../../lib/errors/ConnectionError'
+
+describe(ErrorReporter.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/errors/normalizeError.tests.js b/app/__tests__/errors/normalizeError.tests.js
new file mode 100644
index 00000000..d338222d
--- /dev/null
+++ b/app/__tests__/errors/normalizeError.tests.js
@@ -0,0 +1,8 @@
+import { normalizeError } from '../../lib/errors/normalizeError'
+// import { MeteorError } from '../../lib/errors/MeteorError'
+// import { AuthenticationError } from '../../lib/errors/AuthenticationError'
+// import { ConnectionError } from '../../lib/errors/ConnectionError'
+
+describe(normalizeError.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useBackHandler.tests.js b/app/__tests__/hooks/useBackHandler.tests.js
new file mode 100644
index 00000000..6ca3cb68
--- /dev/null
+++ b/app/__tests__/hooks/useBackHandler.tests.js
@@ -0,0 +1,5 @@
+import { useBackHandler } from '../../lib/hooks/useBackHandler'
+
+describe(useBackHandler.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useConnection.tests.js b/app/__tests__/hooks/useConnection.tests.js
new file mode 100644
index 00000000..5383c92b
--- /dev/null
+++ b/app/__tests__/hooks/useConnection.tests.js
@@ -0,0 +1,5 @@
+// import { useConnection } from '../../lib/hooks/useConnection'
+
+describe('useConnection', function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useKeyboardVisibilityHandler.tests.js b/app/__tests__/hooks/useKeyboardVisibilityHandler.tests.js
new file mode 100644
index 00000000..83eb3dc2
--- /dev/null
+++ b/app/__tests__/hooks/useKeyboardVisibilityHandler.tests.js
@@ -0,0 +1,5 @@
+import { useKeyboardVisibilityHandler } from '../../lib/hooks/useKeyboardVisibilityHandler'
+
+describe(useKeyboardVisibilityHandler.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useLogin.tests.js b/app/__tests__/hooks/useLogin.tests.js
new file mode 100644
index 00000000..ff396f89
--- /dev/null
+++ b/app/__tests__/hooks/useLogin.tests.js
@@ -0,0 +1,5 @@
+import { useLogin } from '../../lib/hooks/useLogin'
+
+describe(useLogin.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useProgress.tests.js b/app/__tests__/hooks/useProgress.tests.js
new file mode 100644
index 00000000..a07a926a
--- /dev/null
+++ b/app/__tests__/hooks/useProgress.tests.js
@@ -0,0 +1,5 @@
+import { useProgress } from '../../lib/hooks/useProgress'
+
+describe(useProgress.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useScreenIsActive.tests.js b/app/__tests__/hooks/useScreenIsActive.tests.js
new file mode 100644
index 00000000..603ebaeb
--- /dev/null
+++ b/app/__tests__/hooks/useScreenIsActive.tests.js
@@ -0,0 +1,5 @@
+import { useScreenIsActive } from '../../lib/hooks/screenIsActive'
+
+describe(useScreenIsActive.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useTimeout.tests.js b/app/__tests__/hooks/useTimeout.tests.js
new file mode 100644
index 00000000..a9d79354
--- /dev/null
+++ b/app/__tests__/hooks/useTimeout.tests.js
@@ -0,0 +1,5 @@
+import { useTimeout } from '../../lib/hooks/useTimeout'
+
+describe(useTimeout.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useUser.tests.js b/app/__tests__/hooks/useUser.tests.js
new file mode 100644
index 00000000..21351ae0
--- /dev/null
+++ b/app/__tests__/hooks/useUser.tests.js
@@ -0,0 +1,5 @@
+import { useUser } from '../../lib/hooks/useUser'
+
+describe(useUser.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/hooks/useVoices.tests.js b/app/__tests__/hooks/useVoices.tests.js
new file mode 100644
index 00000000..091bf678
--- /dev/null
+++ b/app/__tests__/hooks/useVoices.tests.js
@@ -0,0 +1,5 @@
+import { useVoices } from '../../lib/hooks/useVoices'
+
+describe(useVoices.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/i18n/i18n.test.js b/app/__tests__/i18n/i18n.test.js
new file mode 100644
index 00000000..c7c7b282
--- /dev/null
+++ b/app/__tests__/i18n/i18n.test.js
@@ -0,0 +1,28 @@
+import { i18n } from '../../lib/i18n'
+
+const translationEN = i18n.getDataByLanguage('en')
+const translationDE = i18n.getDataByLanguage('de')
+
+test('recursively iterate all object keys of i18 EN and DE, checks if the same namespaces exists, if namespaces have the same length', () => {
+ const toKeys = (obj, keys = [], path = '') => {
+ Object.entries(obj).forEach(([key, value]) => {
+ const type = typeof value
+ if (value === null || (type !== 'object' && type !== 'string')) {
+ throw new Error(`Expected object|string, got ${value}`)
+ }
+ const newPath = `${path}.${key}`
+ if (type === 'string') {
+ keys.push(newPath)
+ }
+ else {
+ toKeys(value, keys, newPath)
+ }
+ })
+ return keys
+ }
+ const byName = (a,b) => a.localeCompare(b)
+ const deKeys = toKeys(translationDE).sort(byName)
+ const enKeys = toKeys(translationEN).sort(byName)
+
+ expect(deKeys).toEqual(enKeys)
+})
diff --git a/app/__tests__/infrastructure/app/AppTemrinate.tests.js b/app/__tests__/infrastructure/app/AppTemrinate.tests.js
new file mode 100644
index 00000000..428e91c7
--- /dev/null
+++ b/app/__tests__/infrastructure/app/AppTemrinate.tests.js
@@ -0,0 +1,5 @@
+import { AppTerminate } from '../../../lib/infrastructure/app/AppTerminate'
+
+describe(AppTerminate.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/infrastructure/collections/collection.tests.js b/app/__tests__/infrastructure/collections/collection.tests.js
new file mode 100644
index 00000000..4b970bb1
--- /dev/null
+++ b/app/__tests__/infrastructure/collections/collection.tests.js
@@ -0,0 +1,5 @@
+// import { getCollection, addCollection } from '../../../lib/infrastructure/collections/collections'
+
+describe('collections', function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/infrastructure/createRepository.tests.js b/app/__tests__/infrastructure/createRepository.tests.js
new file mode 100644
index 00000000..dbda7a9c
--- /dev/null
+++ b/app/__tests__/infrastructure/createRepository.tests.js
@@ -0,0 +1,17 @@
+import { createRepository } from '../../lib/infrastructure/createRepository'
+import { simpleRandom } from '../../__testHelpers__/simpleRandom'
+
+describe(createRepository.name, function () {
+ it('creates a repository-pattern impl', () => {
+ const repo = createRepository()
+ const name = simpleRandom()
+ const value = simpleRandom()
+
+ expect(repo.has(name)).toEqual(false)
+ repo.add(name, value)
+ expect(repo.has(name)).toEqual(true)
+ expect(repo.get(name)).toEqual(value)
+ expect(() => repo.add(name, value))
+ .toThrow(`Entry "${name}" already exists`)
+ })
+})
diff --git a/app/__tests__/infrastructure/factories/createCollection.tests.js b/app/__tests__/infrastructure/factories/createCollection.tests.js
new file mode 100644
index 00000000..a55ef29d
--- /dev/null
+++ b/app/__tests__/infrastructure/factories/createCollection.tests.js
@@ -0,0 +1,5 @@
+import { createCollection } from '../../../lib/infrastructure/createCollection'
+
+describe(createCollection.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/infrastructure/log/log.tests.js b/app/__tests__/infrastructure/log/log.tests.js
new file mode 100644
index 00000000..ad48bf3d
--- /dev/null
+++ b/app/__tests__/infrastructure/log/log.tests.js
@@ -0,0 +1,5 @@
+import { Log } from '../../../lib/infrastructure/Log'
+
+describe(Log.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/choice/Choice.tests.js b/app/__tests__/items/choice/Choice.tests.js
new file mode 100644
index 00000000..62bdf01f
--- /dev/null
+++ b/app/__tests__/items/choice/Choice.tests.js
@@ -0,0 +1,190 @@
+import { Choice } from '../../../lib/items/choice/Choice'
+import { scoreChoice } from '../../../lib/items/choice/scoring'
+import { simpleRandom } from '../../../__testHelpers__/simpleRandom'
+import { Scoring } from '../../../lib/scoring/Scoring'
+import { toInteger } from '../../../lib/utils/number/toInteger'
+import { isUndefinedResponse } from '../../../lib/scoring/isUndefinedResponse'
+
+const createItemDoc = ({ flavor, competency, correctResponse, requires } = {}) => {
+ return {
+ flavor: flavor ?? Choice.flavors.single.value,
+ scoring: [{
+ competency: competency ?? simpleRandom(),
+ correctResponse: correctResponse ?? Math.round(Math.random() * 100).toString(10),
+ requires: requires ?? simpleRandom()
+ }]
+ }
+}
+
+describe(Choice.name, () => {
+ describe(scoreChoice.name, () => {
+ it('throws when there is an unknown flavor', () => {
+ [
+ { scoring: [{}] },
+ { scoring: [{}], flavor: -99 }
+ ].forEach(value => {
+ expect(() => scoreChoice(value))
+ .toThrow(`Unexpected choice flavor: ${value.flavor}`)
+ })
+ })
+
+ describe(Choice.flavors.single.name, () => {
+ it('scores an undefined single choice response', () => {
+ const itemDoc = createItemDoc()
+
+ ;['', null, undefined, Scoring.UNDEFINED].forEach(value => {
+ const responseDoc = { responses: [value] }
+ expect(scoreChoice(itemDoc, responseDoc)).toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value,
+ score: false,
+ isUndefined: true
+ }])
+ })
+ })
+ it('scores a truthy single choice response', () => {
+ const itemDoc = createItemDoc()
+ const responseDoc = { responses: [itemDoc.scoring[0].correctResponse] }
+ const result = scoreChoice(itemDoc, responseDoc)
+ expect(result).toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: Number.parseInt(itemDoc.scoring[0].correctResponse),
+ score: true,
+ isUndefined: false
+ }])
+ })
+ it('scores a falsy single choice response', () => {
+ const itemDoc = createItemDoc()
+ const responseDoc = { responses: ['-99'] }
+ const result = scoreChoice(itemDoc, responseDoc)
+ expect(result).toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: -99,
+ score: false,
+ isUndefined: false
+ }])
+ })
+ })
+
+ describe(Choice.flavors.multiple.name, () => {
+ it('scores undefined multiple choice response', () => {
+ const itemDoc = createItemDoc({
+ flavor: Choice.flavors.multiple.value,
+ correctResponse: [3, 18],
+ requires: Scoring.types.all.value
+ })
+
+ ;[[], [''], [null], [Scoring.UNDEFINED], undefined, null, '', Scoring.UNDEFINED]
+ .forEach(responses => {
+ const responseDoc = { responses }
+ const result = scoreChoice(itemDoc, responseDoc)
+ expect(result).toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: responses === undefined ? [] : responses,
+ score: false,
+ isUndefined: true
+ }])
+ })
+ })
+ it('throws when there is an unknown scoring type', () => {
+ const itemDoc = createItemDoc({
+ flavor: Choice.flavors.multiple.value,
+ correctResponse: [3, 18],
+ requires: -99
+ })
+ const reponseDoc = { responses: ['3', '18'] }
+ expect(() => scoreChoice(itemDoc, reponseDoc))
+ .toThrow('Unexpected scoring type: -99')
+ })
+
+ describe(Scoring.types.all.name, () => {
+ it('scores a truthy multiple choice response (requires all)', () => {
+ const itemDoc = createItemDoc({
+ flavor: Choice.flavors.multiple.value,
+ correctResponse: [3, 18],
+ requires: Scoring.types.all.value
+ })
+ const responseDoc = { responses: ['3', '18'] }
+ const result = scoreChoice(itemDoc, responseDoc)
+ expect(result).toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: [3, 18].sort(),
+ score: true,
+ isUndefined: false
+ }])
+ })
+ it('scores a falsy multiple choice response (requires all)', () => {
+ const itemDoc = createItemDoc({
+ flavor: Choice.flavors.multiple.value,
+ correctResponse: [3, 18],
+ requires: Scoring.types.all.value
+ })
+
+ ;[['3'], ['18'], ['3', '18', '24'], ['2'], ['2', '20'], ['2', '20', '30']]
+ .forEach(responses => {
+ const responseDoc = { responses }
+ const result = scoreChoice(itemDoc, responseDoc)
+ expect(result).toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: responses.map(toInteger).sort(),
+ score: false,
+ isUndefined: false
+ }])
+ })
+ })
+ })
+
+ describe(Scoring.types.any.name, () => {
+ it('scores a truthy multiple choice response (requires any)', () => {
+ const itemDoc = createItemDoc({
+ flavor: Choice.flavors.multiple.value,
+ correctResponse: [3, 18],
+ requires: Scoring.types.any.value
+ })
+
+ ;[['3'], ['18'], ['3', '15'], [Scoring.UNDEFINED, '3'], ['1', '18', '20']]
+ .forEach(responses => {
+ const responseDoc = { responses }
+ const result = scoreChoice(itemDoc, responseDoc)
+ expect(result).toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: responses.map(value => {
+ if (isUndefinedResponse(value)) return undefined
+ return toInteger(value)
+ }),
+ score: true,
+ isUndefined: false
+ }])
+ })
+ })
+ it('scores a falsy multiple choice response (requires any)', () => {
+ const itemDoc = createItemDoc({
+ flavor: Choice.flavors.multiple.value,
+ correctResponse: [3, 18],
+ requires: Scoring.types.any.value
+ })
+
+ ;[['1'], ['20', '23']]
+ .forEach(responses => {
+ const responseDoc = { responses }
+ const result = scoreChoice(itemDoc, responseDoc)
+ expect(result).toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: responses.map(toInteger).sort(),
+ score: false,
+ isUndefined: false
+ }])
+ })
+ })
+ })
+ })
+ })
+})
diff --git a/app/__tests__/items/choice/ChoiceRenderer.tests.js b/app/__tests__/items/choice/ChoiceRenderer.tests.js
new file mode 100644
index 00000000..80b6e539
--- /dev/null
+++ b/app/__tests__/items/choice/ChoiceRenderer.tests.js
@@ -0,0 +1,5 @@
+import { ChoiceRenderer } from '../../../lib/items/choice/ChoiceRenderer'
+
+describe(ChoiceRenderer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/choice/ScoreChoice.tests.js b/app/__tests__/items/choice/ScoreChoice.tests.js
new file mode 100644
index 00000000..443555e4
--- /dev/null
+++ b/app/__tests__/items/choice/ScoreChoice.tests.js
@@ -0,0 +1,5 @@
+import { scoreChoice } from '../../../lib/items/choice/scoring'
+
+describe(scoreChoice.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/choice/getChoiceEntryScoreColor.tests.js b/app/__tests__/items/choice/getChoiceEntryScoreColor.tests.js
new file mode 100644
index 00000000..a3a02e4e
--- /dev/null
+++ b/app/__tests__/items/choice/getChoiceEntryScoreColor.tests.js
@@ -0,0 +1,5 @@
+import { getChoiceEntryScoreColor } from '../../../lib/items/choice/getChoiceEntryScoreColor'
+
+describe(getChoiceEntryScoreColor.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/cloze/ClozeRenderer.tests.js b/app/__tests__/items/cloze/ClozeRenderer.tests.js
new file mode 100644
index 00000000..dcbd35c2
--- /dev/null
+++ b/app/__tests__/items/cloze/ClozeRenderer.tests.js
@@ -0,0 +1,5 @@
+import { ClozeRenderer } from '../../../lib/items/cloze/ClozeRenderer'
+
+describe(ClozeRenderer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/cloze/Clozetokenizer.tests.js b/app/__tests__/items/cloze/Clozetokenizer.tests.js
new file mode 100644
index 00000000..e1bcddd4
--- /dev/null
+++ b/app/__tests__/items/cloze/Clozetokenizer.tests.js
@@ -0,0 +1,691 @@
+import {
+ ClozeTokenizer,
+ tokenizeBlanks,
+ tokenizeSelect,
+ tokenizeText,
+ toTokens,
+ getTokenValueForFlavor
+} from '../../../lib/items/cloze/ClozeTokenizer'
+import { Cloze } from '../../../lib/items/cloze/Cloze'
+
+describe('ClozeTokenizer', function () {
+ describe(getTokenValueForFlavor.name, function () {
+ it('throws on undefined flavour', function () {
+ [-1, 0, 5, true, false, {}, undefined, null].forEach(flavor => {
+ expect(() => getTokenValueForFlavor(flavor))
+ .toThrow(`Unexpected flavor: ${flavor}`)
+ })
+ })
+ })
+ describe(tokenizeBlanks.name, function () {
+ it('it splits a blanks value into the correct tokens', function () {
+ const flavor = '99'
+
+ expect(tokenizeBlanks(flavor, '[foo]')).toEqual([{
+ hasPre: false,
+ hasSuf: false,
+ flavor,
+ isToken: true,
+ index: 0,
+ length: 3,
+ value: 'foo'
+ }])
+
+ expect(tokenizeBlanks(flavor, 'ha [foo] bar')).toEqual([{
+ index: 0,
+ length: 3,
+ value: 'ha '
+ }, {
+ hasPre: true,
+ hasSuf: true,
+ flavor,
+ isToken: true,
+ index: 1,
+ length: 3,
+ value: 'foo'
+ }, {
+ index: 2,
+ length: 4,
+ value: ' bar'
+ }])
+ })
+ it('it splits a empty value into the correct tokens', function () {
+ const flavor = '99'
+
+ expect(tokenizeBlanks(flavor, '[foo]')).toEqual([{
+ hasPre: false,
+ hasSuf: false,
+ flavor,
+ isToken: true,
+ index: 0,
+ length: 3,
+ value: 'foo'
+ }])
+
+ expect(tokenizeBlanks(flavor, 'ha [foo] bar')).toEqual([{
+ index: 0,
+ length: 3,
+ value: 'ha '
+ }, {
+ hasPre: true,
+ hasSuf: true,
+ flavor,
+ isToken: true,
+ index: 1,
+ length: 3,
+ value: 'foo'
+ }, {
+ index: 2,
+ length: 4,
+ value: ' bar'
+ }])
+ })
+ })
+ describe(tokenizeSelect.name, function () {
+ it('correctly tokenizes a select value', function () {
+ const flavor = '99'
+
+ expect(tokenizeSelect(flavor, '[foo|bar]')).toEqual([{
+ hasPre: false,
+ hasSuf: false,
+ flavor,
+ isToken: true,
+ index: 0,
+ length: 7,
+ value: ['foo', 'bar']
+ }])
+
+ expect(tokenizeSelect(flavor, 'ha [foo|bar|baz] bar')).toEqual([{
+ index: 0,
+ length: 3,
+ value: 'ha '
+ }, {
+ hasPre: true,
+ hasSuf: true,
+ flavor,
+ isToken: true,
+ index: 1,
+ length: 11,
+ value: ['foo', 'bar', 'baz']
+ }, {
+ index: 2,
+ length: 4,
+ value: ' bar'
+ }])
+ })
+ })
+ describe(tokenizeText.name, function () {
+ it('it splits a text value into the correct tokens', function () {
+ const flavor = '99'
+
+ expect(tokenizeText(flavor, '[foo]')).toEqual([{
+ hasPre: false,
+ hasSuf: false,
+ flavor,
+ isToken: true,
+ index: 0,
+ length: 3,
+ value: 'foo'
+ }])
+
+ expect(tokenizeText(flavor, 'ha [foo] bar')).toEqual([{
+ index: 0,
+ length: 3,
+ value: 'ha '
+ }, {
+ hasPre: false,
+ hasSuf: false,
+ flavor,
+ isToken: true,
+ index: 1,
+ length: 3,
+ value: 'foo'
+ }, {
+ index: 2,
+ length: 4,
+ value: ' bar'
+ }])
+ })
+ })
+ describe(toTokens.name, function () {
+ it('throws on unexpected flavour', function () {
+ const flavour = Math.random().toString(16)
+ expect(() => toTokens({ value: `${flavour}$foo$bar` }))
+ .toThrow(`Unexpected flavor - ${flavour}`)
+ })
+ it('throws if pattern uses an insufficient syntax', function () {
+ expect(() => toTokens({ value: 'blanks$[foo]$bar$moo' }))
+ .toThrow('Invalid options syntax: moo')
+ expect(() => toTokens({ value: 'blanks$[foo]$bar$moo=' }))
+ .toThrow('Invalid options syntax: moo')
+ expect(() => toTokens({ value: 'blanks$[foo]$bar$moo=buya&bla' }))
+ .toThrow('Invalid options syntax: bla')
+ })
+ it('allows to map splits to renderable tokens', function () {
+ expect(toTokens({ value: '//' })).toEqual({
+ value: '//',
+ isNewLine: true
+ })
+ expect(toTokens({ value: 'noseparator' })).toEqual({ value: 'noseparator' })
+ expect(toTokens({ value: 'blanks$foo$bar' })).toEqual({
+ flavor: 2,
+ isBlock: false,
+ tts: 'bar',
+ value: [
+ {
+ index: 0,
+ length: 3,
+ value: 'foo'
+ }
+ ]
+ })
+ expect(toTokens({ value: 'blanks$[foo]$bar' })).toEqual({
+ flavor: 2,
+ isBlock: false,
+ tts: 'bar',
+ value: [
+ {
+ flavor: 2,
+ hasPre: false,
+ hasSuf: false,
+ index: 0,
+ isToken: true,
+ length: 3,
+ value: 'foo'
+ }
+ ]
+ })
+ expect(toTokens({ value: 'select$[foo|baz]$bar' })).toEqual({
+ flavor: 1,
+ isBlock: false,
+ tts: 'bar',
+ value: [
+ {
+ flavor: 1,
+ hasPre: false,
+ hasSuf: false,
+ index: 0,
+ isToken: true,
+ length: 7,
+ value: ['foo', 'baz']
+ }
+ ]
+ })
+ expect(toTokens({ value: 'empty$[foo]$bar' })).toEqual({
+ flavor: 3,
+ isBlock: false,
+ tts: 'bar',
+ value: [{
+ flavor: 3,
+ hasPre: false,
+ hasSuf: false,
+ index: 0,
+ isToken: true,
+ length: 3,
+ value: 'foo'
+ }
+ ]
+ })
+ expect(toTokens({ value: 'text$[foo]$bar' })).toEqual({
+ flavor: 4,
+ isBlock: false,
+ tts: 'bar',
+ value: [
+ {
+ flavor: 4,
+ hasPre: false,
+ hasSuf: false,
+ index: 0,
+ isToken: true,
+ length: 3,
+ value: 'foo'
+ }
+ ]
+ })
+ })
+ it('supports options but optional', function () {
+ expect(toTokens({ value: 'blanks$foo$bar$color=primary' })).toEqual({
+ flavor: 2,
+ isBlock: false,
+ tts: 'bar',
+ color: 'primary',
+ value: [
+ {
+ index: 0,
+ length: 3,
+ value: 'foo'
+ }
+ ]
+ })
+
+ // multiple split by &
+ expect(toTokens({ value: 'blanks$foo$bar$color=primary&border=dark' })).toEqual({
+ flavor: 2,
+ isBlock: false,
+ tts: 'bar',
+ color: 'primary',
+ border: 'dark',
+ value: [
+ {
+ index: 0,
+ length: 3,
+ value: 'foo'
+ }
+ ]
+ })
+ })
+ })
+
+ describe(ClozeTokenizer.tokenize.name, function () {
+ it('tokenizes a default cloze text correctly', function () {
+ const text = `{{blanks$[L]iebe$Liebe}} Frau Lang,
+{{blanks$[L]ara$Lara}} ist {{blanks$[h]eute$heute}} leider krank.`
+
+ const { tokens, tokenIndexes } = ClozeTokenizer.tokenize({ text })
+ expect(tokenIndexes).toEqual([0, 1, 2])
+ expect(tokens).toEqual([
+ {
+ value: '',
+ length: 0,
+ isEmpty: true,
+ index: 0
+ },
+ {
+ isToken: true,
+ value: [
+ {
+ itemIndex: 0,
+ isToken: true,
+ value: 'L',
+ length: 1,
+ index: 0,
+ hasPre: false,
+ hasSuf: true,
+ flavor: 2
+ },
+ {
+ value: 'iebe',
+ length: 4,
+ index: 1
+ }
+ ],
+ length: 20,
+ index: 1,
+ flavor: 2,
+ tts: 'Liebe',
+ isBlock: false
+ },
+ {
+ value: ' Frau Lang, ',
+ length: 12,
+ index: 2
+ },
+ {
+ isToken: true,
+ value: '//',
+ length: 2,
+ index: 3,
+ isNewLine: true
+ },
+ {
+ value: '',
+ length: 0,
+ isEmpty: true,
+ index: 4
+ },
+ {
+ isToken: true,
+ value: [
+ {
+ isToken: true,
+ value: 'L',
+ length: 1,
+ index: 0,
+ hasPre: false,
+ hasSuf: true,
+ flavor: 2,
+ itemIndex: 1
+ },
+ {
+ value: 'ara',
+ length: 3,
+ index: 1
+ }
+ ],
+ length: 18,
+ index: 5,
+ flavor: 2,
+ tts: 'Lara',
+ isBlock: false
+ },
+ {
+ value: ' ist ',
+ length: 5,
+ index: 6
+ },
+ {
+ isToken: true,
+ value: [
+ {
+ isToken: true,
+ value: 'h',
+ length: 1,
+ index: 0,
+ hasPre: false,
+ hasSuf: true,
+ flavor: 2,
+ itemIndex: 2
+ },
+ {
+ value: 'eute',
+ length: 4,
+ index: 1
+ }
+ ],
+ length: 20,
+ index: 7,
+ flavor: 2,
+ tts: 'heute',
+ isBlock: false
+ },
+ {
+ value: ' leider krank.',
+ length: 14,
+ index: 8
+ }
+ ])
+ })
+ it('tokenizes a cloze text in table mode correctly', function () {
+ const text = `Die Zahl: || 41 || {{blanks$[26]$}} || 19 || {{blanks$[21]$}} || {{blanks$[44]$}}
+Das Doppelte: || {{blanks$[82]$}} || 52 || {{blanks$[38]$}} || 42 || 88`
+ const { tokens, tokenIndexes } = ClozeTokenizer.tokenize({ text, isTable: true })
+ expect(tokenIndexes).toEqual([0, 1, 2, 3, 4])
+ expect(tokens).toEqual([
+ // 1. row
+ [
+ {
+ value: 'Die Zahl:',
+ length: 9,
+ index: 0
+ },
+ {
+ value: '41',
+ length: 2,
+ index: 1
+ },
+ {
+ isToken: true,
+ value: [
+ {
+ isToken: true,
+ itemIndex: 0,
+ value: '26',
+ length: 2,
+ index: 0,
+ hasPre: false,
+ hasSuf: false,
+ flavor: 2
+ }
+ ],
+ length: 12,
+ index: 2,
+ flavor: 2,
+ tts: '',
+ isBlock: false
+ },
+ {
+ value: '19',
+ length: 2,
+ index: 3
+ },
+ { // ||
+ isToken: true,
+ value: [
+ {
+ isToken: true,
+ value: '21',
+ itemIndex: 1,
+ length: 2,
+ index: 0,
+ hasPre: false,
+ hasSuf: false,
+ flavor: 2
+ }
+ ],
+ length: 12,
+ index: 4,
+ flavor: 2,
+ tts: '',
+ isBlock: false
+ },
+ {
+ isToken: true,
+ value: [
+ {
+ isToken: true,
+ value: '44',
+ itemIndex: 2,
+ length: 2,
+ index: 0,
+ hasPre: false,
+ hasSuf: false,
+ flavor: 2
+ }
+ ],
+ length: 12,
+ index: 5,
+ flavor: 2,
+ tts: '',
+ isBlock: false
+ }
+ ],
+ // 2. row
+ [
+ {
+ value: 'Das Doppelte:',
+ length: 13,
+ index: 0
+ },
+ {
+ isToken: true,
+ value: [
+ {
+ isToken: true,
+ value: '82',
+ itemIndex: 3,
+ length: 2,
+ index: 0,
+ hasPre: false,
+ hasSuf: false,
+ flavor: 2
+ }
+ ],
+ length: 12,
+ index: 1,
+ flavor: 2,
+ tts: '',
+ isBlock: false
+ },
+ {
+ value: '52',
+ length: 2,
+ index: 2
+ },
+ {
+ isToken: true,
+ value: [
+ {
+ isToken: true,
+ value: '38',
+ itemIndex: 4,
+ length: 2,
+ index: 0,
+ hasPre: false,
+ hasSuf: false,
+ flavor: 2
+ }
+ ],
+ length: 12,
+ index: 3,
+ flavor: 2,
+ tts: '',
+ isBlock: false
+ },
+ {
+ value: '42',
+ length: 2,
+ index: 4
+ },
+ {
+ value: '88',
+ length: 2,
+ index: 5
+ }
+ ]
+ ])
+ })
+ it('tokenizes a cloze table with empties', function () {
+ const text = `<<>> || 1 || 7
+
++ || 6 || 9
+
+<<>> || {{empty$[1]$$pattern=0123456789}} || <<>>
+
+<<>> || {{blanks$[8]$$cellBorder=top&pattern=0123456789}} || {{blanks$[6]$$cellBorder=top&pattern=0123456789}}`
+ const { tokens, tokenIndexes } = ClozeTokenizer.tokenize({ text, isTable: true })
+ expect(tokenIndexes).toEqual([0, 1, 2])
+ expect(tokens).toEqual([
+ // 1. row
+ [
+ {
+ index: 0,
+ isCellSkip: true,
+ length: 4,
+ value: '<<>>'
+ },
+ {
+ index: 1,
+ length: 1,
+ value: '1'
+ },
+ {
+ index: 2,
+ length: 1,
+ value: '7'
+ }
+ ],
+ // 2. row
+ [
+ {
+ index: 0,
+ length: 1,
+ value: '+'
+ },
+ {
+ index: 1,
+ length: 1,
+ value: '6'
+ },
+ {
+ index: 2,
+ length: 1,
+ value: '9'
+ }
+ ],
+ // 3, row
+ [
+ {
+ index: 0,
+ isCellSkip: true,
+ length: 4,
+ value: '<<>>'
+ },
+ {
+ index: 1,
+ flavor: 3,
+ isBlock: false,
+ isToken: true,
+ length: 29,
+ pattern: '0123456789',
+ tts: '',
+ value: [
+ {
+ // empties need an
+ // itemIndex because scoring
+ // references targets by index
+ // that includes empties
+ itemIndex: 0,
+ flavor: Cloze.flavor.empty.value,
+ hasPre: false,
+ hasSuf: false,
+ index: 0,
+ isToken: true,
+ length: 1,
+ value: '1'
+ }
+ ]
+ },
+ {
+ index: 2,
+ isCellSkip: true,
+ length: 4,
+ value: '<<>>'
+ }
+ ],
+ // 4. row
+ [
+ {
+ index: 0,
+ isCellSkip: true,
+ length: 4,
+ value: '<<>>'
+ },
+ {
+ cellBorder: 'top',
+ flavor: Cloze.flavor.blanks.value,
+ index: 1,
+ isBlock: false,
+ isToken: true,
+ length: 45,
+ pattern: '0123456789',
+ tts: '',
+ value: [
+ {
+ itemIndex: 1,
+ flavor: Cloze.flavor.blanks.value,
+ hasPre: false,
+ hasSuf: false,
+ index: 0,
+ isToken: true,
+ length: 1,
+ value: '8'
+ }
+ ]
+ },
+ {
+ cellBorder: 'top',
+ flavor: Cloze.flavor.blanks.value,
+ index: 2,
+ isBlock: false,
+ isToken: true,
+ length: 45,
+ pattern: '0123456789',
+ tts: '',
+ value: [
+ {
+ itemIndex: 2,
+ flavor: Cloze.flavor.blanks.value,
+ hasPre: false,
+ hasSuf: false,
+ index: 0,
+ isToken: true,
+ length: 1,
+ value: '6'
+ }
+ ]
+ }
+ ]
+ ])
+ })
+ })
+})
diff --git a/app/__tests__/items/cloze/createScoringSummaryForInput.tests.js b/app/__tests__/items/cloze/createScoringSummaryForInput.tests.js
new file mode 100644
index 00000000..f455bc5e
--- /dev/null
+++ b/app/__tests__/items/cloze/createScoringSummaryForInput.tests.js
@@ -0,0 +1,52 @@
+import { createScoringSummaryForInput } from '../../../lib/items/cloze/createScoringSummaryForInput'
+import { CompareState } from '../../../lib/items/utils/CompareState'
+import { UndefinedScore } from '../../../lib/scoring/UndefinedScore'
+
+describe(createScoringSummaryForInput.name, function () {
+ it('creates a summary for a single-score response for an input', function () {
+ ['moo', ['moo'], undefined, [undefined], UndefinedScore, [UndefinedScore]].forEach(value => {
+ [true, false].forEach(score => {
+ const expectedColor = CompareState.getColor(score ? 1 : 0)
+ const entries = [{ value, score: score ? 1 : 0 }]
+ const summary = createScoringSummaryForInput({
+ itemIndex: 5,
+ actual: value,
+ entries
+ })
+ expect(summary).toEqual({
+ index: 5,
+ score: score ? 1 : 0,
+ actual: value,
+ color: expectedColor,
+ entries
+ })
+ })
+ })
+ })
+
+ it('creates a summary for a multiple-score response for an input', function () {
+ ['moo', ['moo'], undefined, [undefined], UndefinedScore, [UndefinedScore]].forEach(value => {
+ [0, 1, 2, 3].forEach(trueScores => {
+ const avg = trueScores / 3
+ const expectedColor = CompareState.getColor(Math.floor(avg))
+ const entries = [
+ { value, score: trueScores > 0 ? 1 : 0 },
+ { value, score: trueScores > 1 ? 1 : 0 },
+ { value, score: trueScores > 2 ? 1 : 0 }
+ ]
+ const summary = createScoringSummaryForInput({
+ itemIndex: 5,
+ actual: value,
+ entries
+ })
+ expect(summary).toEqual({
+ index: 5,
+ score: avg,
+ actual: value,
+ color: expectedColor,
+ entries
+ })
+ })
+ })
+ })
+})
diff --git a/app/__tests__/items/cloze/scoreCloze.tests.js b/app/__tests__/items/cloze/scoreCloze.tests.js
new file mode 100644
index 00000000..e0f2a16b
--- /dev/null
+++ b/app/__tests__/items/cloze/scoreCloze.tests.js
@@ -0,0 +1,146 @@
+import { scoreCloze } from '../../../lib/items/cloze/scoring'
+import { Scoring } from '../../../lib/scoring/Scoring'
+import { simpleRandom } from '../../../__testHelpers__/simpleRandom'
+
+const createItemDoc = ({ competency, correctResponse } = {}) => {
+ return {
+ scoring: [{
+ target: 0,
+ competency: competency ?? [simpleRandom(), simpleRandom()],
+ correctResponse: correctResponse ?? /.*/
+ }, {
+ target: 1,
+ competency: competency ?? [simpleRandom(), simpleRandom()],
+ correctResponse: correctResponse ?? /.*/
+ }]
+ }
+}
+
+describe(scoreCloze.name, function () {
+ it('detects if all responses are undefined', () => {
+ const itemDoc = createItemDoc()
+ const allResponses = [
+ [], ['', ''], [undefined, undefined], [null, null], [Scoring.UNDEFINED, Scoring.UNDEFINED]
+ ]
+ allResponses.forEach(responses => {
+ const responseDoc = { responses }
+ expect(scoreCloze(itemDoc, responseDoc))
+ .toEqual([
+ {
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: responseDoc.responses[0],
+ score: false,
+ target: 0,
+ isUndefined: true
+ },
+ {
+ competency: itemDoc.scoring[1].competency,
+ correctResponse: itemDoc.scoring[1].correctResponse,
+ value: responseDoc.responses[1],
+ score: false,
+ target: 1,
+ isUndefined: true
+ }
+ ])
+ })
+ })
+ it('scores correct responses with ture scores', () => {
+ const itemDoc = createItemDoc({
+ correctResponse: /\w+/i
+ })
+ const allResponses = [
+ ['foo', 'bar'], [' bar', '\nbaz'], ['lol', 'mooooo#!']
+ ]
+ allResponses.forEach(responses => {
+ const responseDoc = { responses }
+ expect(scoreCloze(itemDoc, responseDoc))
+ .toEqual([
+ {
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: responseDoc.responses[0],
+ score: true,
+ target: 0,
+ isUndefined: false
+ },
+ {
+ competency: itemDoc.scoring[1].competency,
+ correctResponse: itemDoc.scoring[1].correctResponse,
+ value: responseDoc.responses[1],
+ score: true,
+ target: 1,
+ isUndefined: false
+ }
+ ])
+ })
+ })
+ it('scores mixed true/false responses with respective scores', () => {
+ const itemDoc = {
+ scoring: [{
+ target: 0,
+ competency: [simpleRandom(), simpleRandom()],
+ correctResponse: /^F.*$/
+ }, {
+ target: 0,
+ competency: [simpleRandom(), simpleRandom()],
+ correctResponse: /foo/
+ }, {
+ target: 1,
+ competency: [simpleRandom(), simpleRandom()],
+ correctResponse: /^bar$/
+ }]
+ }
+ expect(scoreCloze(itemDoc, { responses: ['foo', 'bar'] }))
+ .toEqual([{
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: 'foo',
+ score: false,
+ target: 0,
+ isUndefined: false
+ }, {
+ competency: itemDoc.scoring[1].competency,
+ correctResponse: itemDoc.scoring[1].correctResponse,
+ value: 'foo',
+ score: true,
+ target: 0,
+ isUndefined: false
+ }, {
+ competency: itemDoc.scoring[2].competency,
+ correctResponse: itemDoc.scoring[2].correctResponse,
+ value: 'bar',
+ score: true,
+ target: 1,
+ isUndefined: false
+ }])
+ })
+ it('scores true/undefined responses with respective score', () => {
+ const itemDoc = createItemDoc()
+ const allResponses = [
+ ['a'], ['foo', ''], ['moo', undefined], ['bar', null], ['baz', Scoring.UNDEFINED]
+ ]
+ allResponses.forEach((responses) => {
+ const responseDoc = { responses }
+ expect(scoreCloze(itemDoc, responseDoc))
+ .toEqual([
+ {
+ competency: itemDoc.scoring[0].competency,
+ correctResponse: itemDoc.scoring[0].correctResponse,
+ value: responseDoc.responses[0],
+ score: true,
+ target: 0,
+ isUndefined: false
+ },
+ {
+ competency: itemDoc.scoring[1].competency,
+ correctResponse: itemDoc.scoring[1].correctResponse,
+ value: responseDoc.responses[1],
+ score: false,
+ target: 1,
+ isUndefined: true
+ }
+ ])
+ })
+ })
+})
diff --git a/app/__tests__/items/connect/Connect.tests.js b/app/__tests__/items/connect/Connect.tests.js
new file mode 100644
index 00000000..fb000d45
--- /dev/null
+++ b/app/__tests__/items/connect/Connect.tests.js
@@ -0,0 +1,5 @@
+import { Connect } from '../../../lib/items/connect/Connect'
+
+describe(Connect.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/connect/ConnectItemRenderer.tests.js b/app/__tests__/items/connect/ConnectItemRenderer.tests.js
new file mode 100644
index 00000000..22a4dd1d
--- /dev/null
+++ b/app/__tests__/items/connect/ConnectItemRenderer.tests.js
@@ -0,0 +1,5 @@
+import { ConnectItemRenderer } from '../../../lib/items/connect/ConnectItemRenderer'
+
+describe(ConnectItemRenderer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/connect/ScoreConnect.tests.js b/app/__tests__/items/connect/ScoreConnect.tests.js
new file mode 100644
index 00000000..bc32e282
--- /dev/null
+++ b/app/__tests__/items/connect/ScoreConnect.tests.js
@@ -0,0 +1,5 @@
+import { scoreConnect } from '../../../lib/items/connect/scoreConnect'
+
+describe(scoreConnect.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/highlight/Highlight.tests.js b/app/__tests__/items/highlight/Highlight.tests.js
new file mode 100644
index 00000000..c4ce86ba
--- /dev/null
+++ b/app/__tests__/items/highlight/Highlight.tests.js
@@ -0,0 +1,5 @@
+import { Highlight } from '../../../lib/items/highlight/Highlight'
+
+describe(Highlight.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/highlight/HighlightRenderer.tests.js b/app/__tests__/items/highlight/HighlightRenderer.tests.js
new file mode 100644
index 00000000..26b04b51
--- /dev/null
+++ b/app/__tests__/items/highlight/HighlightRenderer.tests.js
@@ -0,0 +1,5 @@
+import { HighlightRenderer } from '../../../lib/items/highlight/HighlightRenderer'
+
+describe(HighlightRenderer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/highlight/HighlightTokenizer.tests.js b/app/__tests__/items/highlight/HighlightTokenizer.tests.js
new file mode 100644
index 00000000..dd82eae3
--- /dev/null
+++ b/app/__tests__/items/highlight/HighlightTokenizer.tests.js
@@ -0,0 +1,5 @@
+import { HighlightTokenizer } from '../../../lib/items/highlight/HighlightTokenizer'
+
+describe(HighlightTokenizer.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/highlight/scoreHighlight.tests.js b/app/__tests__/items/highlight/scoreHighlight.tests.js
new file mode 100644
index 00000000..83006651
--- /dev/null
+++ b/app/__tests__/items/highlight/scoreHighlight.tests.js
@@ -0,0 +1,5 @@
+import { scoreHighlight } from '../../../lib/items/highlight/scoring'
+
+describe(scoreHighlight.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/items/shared/getCompareValuesForSelectableItems.test.js b/app/__tests__/items/shared/getCompareValuesForSelectableItems.test.js
new file mode 100644
index 00000000..d8691a71
--- /dev/null
+++ b/app/__tests__/items/shared/getCompareValuesForSelectableItems.test.js
@@ -0,0 +1,22 @@
+import { getCompareValuesForSelectableItems } from '../../../lib/items/shared/getCompareValuesForSelectableItems'
+
+describe(getCompareValuesForSelectableItems.name, function () {
+ it('scores correct selections as 1, wrong as 0 and missing as -1', () => {
+ const inputs = [
+ // single values
+ [{ 0: true }, [0], { 0: 1 }], // correct
+ [{ 1: true }, [0], { 0: -1, 1: 0 }], // wrong
+ [{}, [3], { 3: -1 }], // no input
+ // multiple values
+ [{ 0: true, 3: true }, [0, 3], { 0: 1, 3: 1 }], // all correct
+ [{ 1: true, 4: true }, [2, 3], { 1: 0, 2: -1, 3: -1, 4: 0 }], // all wrong
+ [{ 1: true, 4: true }, [1, 3], { 1: 1, 3: -1, 4: 0 }], // right and wring
+ [{}, [2, 3], { 2: -1, 3: -1 }] // no input
+ ]
+
+ inputs.forEach(([selected, correctResponses, expected]) => {
+ const result = getCompareValuesForSelectableItems({ selected, correctResponses })
+ expect(result).toEqual(expected)
+ })
+ })
+})
diff --git a/app/__tests__/items/utils/CompareState.tests.js b/app/__tests__/items/utils/CompareState.tests.js
new file mode 100644
index 00000000..428594c3
--- /dev/null
+++ b/app/__tests__/items/utils/CompareState.tests.js
@@ -0,0 +1,41 @@
+import { CompareState } from '../../../lib/items/utils/CompareState'
+import { Colors } from '../../../lib/constants/Colors'
+import { simpleRandom } from '../../../__testHelpers__/simpleRandom'
+import { Scoring } from '../../../lib/scoring/Scoring'
+
+describe('CompareState', () => {
+ describe(CompareState.getColor.name, () => {
+ it('returns the correct color for given values', () => {
+ [
+ { value: -1, color: Colors.missing },
+ { value: 1, color: Colors.right },
+ { value: 0, color: Colors.wrong },
+ { value: simpleRandom(), color: undefined }
+ ].forEach(({ value, color }) => {
+ expect(CompareState.getColor(value)).toEqual(color)
+ })
+ })
+ })
+ describe(CompareState.getValue.name, function () {
+ it('returns the correspondig value by given score and response value', () => {
+ [
+ { score: false, value: 'a', expected: 0 },
+ { score: false, value: {}, expected: 0 },
+ { score: false, value: [], expected: -1 },
+ { score: false, value: '', expected: -1 },
+ { score: false, value: undefined, expected: -1 },
+ { score: false, value: null, expected: -1 },
+ { score: false, value: Scoring.UNDEFINED, expected: -1 },
+ { score: true, value: '', expected: 1 },
+ { score: true, value: 'a', expected: 1 },
+ { score: true, value: [], expected: 1 },
+ { score: true, value: {}, expected: 1 },
+ { score: true, value: undefined, expected: 1 },
+ { score: true, value: null, expected: 1 },
+ { score: true, value: Scoring.UNDEFINED, expected: 1 }
+ ].forEach(({ score, value, expected }) => {
+ expect(CompareState.getValue(score, value)).toEqual(expected)
+ })
+ })
+ })
+})
diff --git a/app/__tests__/items/utils/KeyboardTypes.tests.js b/app/__tests__/items/utils/KeyboardTypes.tests.js
new file mode 100644
index 00000000..5f319506
--- /dev/null
+++ b/app/__tests__/items/utils/KeyboardTypes.tests.js
@@ -0,0 +1,13 @@
+import { KeyboardTypes } from '../../../lib/items/utils/KeyboardTypes'
+
+describe('KeyboardTypes', function () {
+ describe(KeyboardTypes.get.name, function () {
+ test.todo('not implemted')
+ })
+ describe(KeyboardTypes.allowedValues.name, function () {
+ test.todo('not implemted')
+ })
+ describe(KeyboardTypes.register.name, function () {
+ test.todo('not implemted')
+ })
+})
diff --git a/app/__tests__/schema/schema.tests.js b/app/__tests__/schema/schema.tests.js
new file mode 100644
index 00000000..21f19410
--- /dev/null
+++ b/app/__tests__/schema/schema.tests.js
@@ -0,0 +1,29 @@
+import { createSchema } from '../../lib/schema/createSchema'
+import { isSchemaInstance } from '../../lib/schema/isSchemaInstance'
+import { check } from '../../lib/schema/check'
+
+it('creates a new schema instance', function () {
+ const schema = createSchema({ foo: String })
+ expect(isSchemaInstance(schema)).toBe(true)
+})
+
+it('uses check to check against a schema', function () {
+ const schema = createSchema({ foo: String })
+ expect(() => check({}, schema)).toThrow('foo is required')
+ expect(() => check({ foo: 1 }, schema)).toThrow('foo must be of type String')
+})
+
+it('creates a schema on the fly in check when no schema is passed', function () {
+ expect(() => check({}, { foo: String })).toThrow('foo is required')
+ expect(() => check({ foo: 1 }, { foo: String })).toThrow('foo must be of type String')
+
+ const arraySchema = {
+ foo: {
+ type: Array,
+ min: 1
+ },
+ 'foo.$': String
+ }
+ expect(() => check({ foo: [1] }, arraySchema)).toThrow('foo must be of type String')
+ expect(() => check(1, String)).toThrow('target must be of type String')
+})
diff --git a/app/__tests__/schema/settingsSchema.tests.js b/app/__tests__/schema/settingsSchema.tests.js
new file mode 100644
index 00000000..e2ccb75a
--- /dev/null
+++ b/app/__tests__/schema/settingsSchema.tests.js
@@ -0,0 +1,10 @@
+import { isSchemaInstance } from '../../lib/schema/isSchemaInstance'
+import settings from '../../settings/settings.json'
+import { settingsSchema } from '../../lib/settingsSchema'
+
+describe('settingsSchema', function () {
+ it('verifies the schema', () => {
+ expect(isSchemaInstance(settingsSchema)).toBe(true)
+ settingsSchema.validate(settings)
+ })
+})
diff --git a/app/__tests__/schema/validateSettingsSchema.tests.js b/app/__tests__/schema/validateSettingsSchema.tests.js
new file mode 100644
index 00000000..7c504cd7
--- /dev/null
+++ b/app/__tests__/schema/validateSettingsSchema.tests.js
@@ -0,0 +1,7 @@
+import { validateSettingsSchema } from '../../lib/schema/validateSettingsSchema'
+
+describe(validateSettingsSchema.name, function () {
+ it('validates the settings schema', () => {
+ validateSettingsSchema()
+ })
+})
diff --git a/app/__tests__/scoring/Scoring.tests.js b/app/__tests__/scoring/Scoring.tests.js
new file mode 100644
index 00000000..a82407e2
--- /dev/null
+++ b/app/__tests__/scoring/Scoring.tests.js
@@ -0,0 +1,48 @@
+import { Scoring } from '../../lib/scoring/Scoring'
+import { simpleRandom } from '../../__testHelpers__/simpleRandom'
+import { expectThrowAsync } from '../../__testHelpers__/expectThrowAsync'
+
+describe(Scoring.name, function () {
+ describe(Scoring.score.name, () => {
+ it('throws if an invalid itemDoc is given', async () => {
+ await expectThrowAsync({
+ fn: () => Scoring.score(),
+ message: 'Expected itemDoc to have property "scoring"'
+ })
+ await expectThrowAsync({
+ fn: () => Scoring.score({}),
+ message: 'Expected itemDoc to have property "scoring"'
+ })
+ })
+ it('throws if an invalid responseDoc is given', async () => {
+ await expectThrowAsync({
+ fn: () => Scoring.score({ scoring: [{}] }),
+ message: 'Expected responseDoc, got undefined'
+ })
+ await expectThrowAsync({
+ fn: () => Scoring.score({ scoring: [{}] }, {}),
+ message: 'Expected responses to have Array-like property "responses"'
+ })
+ })
+ it('throws if there is no scoring handler found by options', async () => {
+ const type = simpleRandom()
+ const subtype = simpleRandom()
+ const options = { type, subtype, scoring: [{}] }
+
+ await expectThrowAsync({
+ fn: () => Scoring.score(options, { responses: [''] }),
+ message: `Expected scoring fn by ${type} / ${subtype}`
+ })
+ })
+ it('executes the respective registered scoring handler', async () => {
+ const type = simpleRandom()
+ const subtype = simpleRandom()
+ const expectedResult = simpleRandom()
+ const scoreFn = () => expectedResult
+ const options = { type, subtype, scoring: [{}] }
+ Scoring.register({ type, subtype, scoreFn })
+ const result = await Scoring.score(options, { responses: [] })
+ expect(result).toEqual(expectedResult)
+ })
+ })
+})
diff --git a/app/__tests__/scoring/getScoring.tests.js b/app/__tests__/scoring/getScoring.tests.js
new file mode 100644
index 00000000..2c175813
--- /dev/null
+++ b/app/__tests__/scoring/getScoring.tests.js
@@ -0,0 +1,5 @@
+import { getScoring } from '../../lib/scoring/getScoring'
+
+describe(getScoring.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/screens/BaseScreen.tests.js b/app/__tests__/screens/BaseScreen.tests.js
new file mode 100644
index 00000000..3f946848
--- /dev/null
+++ b/app/__tests__/screens/BaseScreen.tests.js
@@ -0,0 +1,5 @@
+import { ScreenBase } from '../../lib/screens/BaseScreen'
+
+describe(ScreenBase.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/screens/complete/Celebrate.tests.js b/app/__tests__/screens/complete/Celebrate.tests.js
new file mode 100644
index 00000000..0dd24d8f
--- /dev/null
+++ b/app/__tests__/screens/complete/Celebrate.tests.js
@@ -0,0 +1,5 @@
+import { Celebrate } from '../../../lib/screens/complete/Celebrate'
+
+describe(Celebrate.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/screens/complete/CompleteScreen.tests.js b/app/__tests__/screens/complete/CompleteScreen.tests.js
new file mode 100644
index 00000000..aa4562ec
--- /dev/null
+++ b/app/__tests__/screens/complete/CompleteScreen.tests.js
@@ -0,0 +1,5 @@
+import { CompleteScreen } from '../../../lib/screens/complete/CompleteScreen'
+
+describe(CompleteScreen.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/screens/complete/generateFeedback.tests.js b/app/__tests__/screens/complete/generateFeedback.tests.js
new file mode 100644
index 00000000..76d9e237
--- /dev/null
+++ b/app/__tests__/screens/complete/generateFeedback.tests.js
@@ -0,0 +1,48 @@
+import { generateFeedback } from '../../../lib/screens/complete/generateFeedback'
+import { Feedback } from '../../../lib/contexts/Feedback'
+
+const fallback = Feedback.getFallbackDoc()
+
+describe(generateFeedback.name, function () {
+ it('returns a fallback doc if nothing is found', () => {
+ const data = [{}, { threshold: 0 }, { feedbackDocs: [] }, { threshold: 0, feedbackDocs: [] }]
+ data.forEach(({ threshold, feedbackDocs }) => {
+ const feedback = generateFeedback({ threshold, feedbackDocs })
+ expect(feedback.percent).toBe(0)
+ expect(feedback.isFallback).toBe(true)
+ expect(feedback.phrase).toBe(fallback.phrases[0])
+ })
+ })
+ it('falls back to the first doc in list, if no doc is suitable for the threshold', () => {
+ const feedbackDocs = [{ threshold: 0.2, phrases: ['foo'] }, { threshold: 0.5, phrases: ['bar'] }]
+ const data = [{}, { threshold: 0 }, { threshold: 0.1 }]
+
+ data.forEach(({ threshold }) => {
+ const feedback = generateFeedback({ threshold, feedbackDocs })
+ expect(feedback.percent).toBe(Math.round((threshold ?? 0) * 100))
+ expect(feedback.phrase).toBe('foo')
+ expect(feedback.isFallback).toBe(false)
+ })
+ })
+
+ it('returns the appropriate feedback for the current threshold', () => {
+ const feedbackDocs = [
+ { threshold: 0.2, phrases: ['foo', 'bar'] },
+ { threshold: 0.5, phrases: ['baz', 'moo'] }
+ ]
+
+ for (let i = 0.2; i < 0.5; i += 0.01) {
+ const feedback = generateFeedback({ threshold: i, feedbackDocs })
+ expect(feedback.percent).toEqual(Math.round(i * 100))
+ expect(feedbackDocs[0].phrases.includes(feedback.phrase)).toBe(true)
+ expect(feedback.isFallback).toBe(false)
+ }
+
+ for (let i = 0.5; i <= 1; i += 0.01) {
+ const feedback = generateFeedback({ threshold: i, feedbackDocs })
+ expect(feedback.percent).toEqual(Math.round(i * 100))
+ expect(feedbackDocs[1].phrases.includes(feedback.phrase)).toBe(true)
+ expect(feedback.isFallback).toBe(false)
+ }
+ })
+})
diff --git a/app/__tests__/screens/complete/loadCompleteData.tests.js b/app/__tests__/screens/complete/loadCompleteData.tests.js
new file mode 100644
index 00000000..77343ceb
--- /dev/null
+++ b/app/__tests__/screens/complete/loadCompleteData.tests.js
@@ -0,0 +1,5 @@
+import { loadCompleteData } from '../../../lib/screens/complete/loadCompleteData'
+
+describe(loadCompleteData.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/screens/home/HomeScreen.tests.js b/app/__tests__/screens/home/HomeScreen.tests.js
new file mode 100644
index 00000000..9a6b3cf1
--- /dev/null
+++ b/app/__tests__/screens/home/HomeScreen.tests.js
@@ -0,0 +1,5 @@
+import { HomeScreen } from '../../../lib/screens/home/HomeScreen'
+
+describe(HomeScreen.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/screens/home/laodHomeData.tests.js b/app/__tests__/screens/home/laodHomeData.tests.js
new file mode 100644
index 00000000..9a6b3cf1
--- /dev/null
+++ b/app/__tests__/screens/home/laodHomeData.tests.js
@@ -0,0 +1,5 @@
+import { HomeScreen } from '../../../lib/screens/home/HomeScreen'
+
+describe(HomeScreen.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/screens/map/loadMapData.tests.js b/app/__tests__/screens/map/loadMapData.tests.js
new file mode 100644
index 00000000..83aae763
--- /dev/null
+++ b/app/__tests__/screens/map/loadMapData.tests.js
@@ -0,0 +1,261 @@
+import Meteor from '@meteorrn/core'
+import { Dimension } from '../../../lib/contexts/Dimension'
+import { stub, restoreAll } from '../../../__testHelpers__/stub'
+import { simpleRandom } from '../../../__testHelpers__/simpleRandom'
+import { loadMapData } from '../../../lib/screens/map/loadMapData'
+import { toDocId } from '../../../lib/utils/array/toDocId'
+import { mockCollection, resetCollection, restoreCollection } from '../../../__testHelpers__/mockCollection'
+import { byDocId } from '../../../lib/utils/array/byDocId'
+import { MapIcons } from '../../../lib/contexts/MapIcons'
+import { Order } from '../../../lib/contexts/Order'
+import { mockCall } from '../../../__testHelpers__/mockCall'
+
+describe(loadMapData.name, () => {
+ beforeAll(() => {
+ mockCollection(Dimension)
+ mockCollection(Order)
+ mockCollection(MapIcons)
+ })
+
+ afterEach(() => {
+ restoreAll()
+ resetCollection(Dimension)
+ resetCollection(Order)
+ resetCollection(MapIcons)
+ })
+
+ afterAll(() => {
+ restoreCollection(Dimension)
+ restoreCollection(Order)
+ restoreCollection(MapIcons)
+ })
+
+ it('returns an "empty" message if the server responded with no or faulty map data', async () => {
+ const fieldDoc = { _id: simpleRandom() }
+
+
+ const allData = [
+ undefined,
+ null,
+ {},
+ { dimensions: [{}] },
+ { dimensions: [{}], entries: [{}] },
+ { levels: [{}], entries: [{}] },
+ { dimensions: [{}], levels: [{}] }
+ ]
+
+ let index = 0
+
+ mockCall((name, args, cb) => cb(undefined, allData[index++]))
+
+ for (const input of allData) {
+ const data = await loadMapData({ fieldDoc, input })
+ expect(data).toEqual({ empty: true })
+ }
+ })
+
+ it('loads the map data without user progress', async () => {
+ const fieldDoc = { _id: simpleRandom(), title: simpleRandom() }
+ const dimensions = [
+ { _id: simpleRandom(), title: simpleRandom(), shortCode: 'R' },
+ { _id: simpleRandom(), title: simpleRandom(), shortCode: 'W' }
+ ]
+
+ const DimensionCollection = Dimension.collection()
+
+ stub(DimensionCollection, 'findOne', (_id) => {
+ return dimensions.find((byDocId(_id)))
+ })
+
+ const levels = [
+ { _id: simpleRandom() },
+ { _id: simpleRandom() }
+ ]
+
+ const mapData = {
+ dimensions: dimensions.map(doc => ({ _id: doc._id, maxProgress: 123, maxCompetencies: 456 })),
+ levels: levels.map(toDocId),
+ entries: [{}]
+ }
+
+ mockCall((name, args, cb) => setTimeout(() => cb(undefined, mapData)))
+
+ const data = await loadMapData({ fieldDoc, loadUserData: null })
+ expect(data.fieldName).toEqual(fieldDoc.title)
+ expect(data.dimensionsResolved).toEqual(true)
+ expect(data.dimensions).toEqual(dimensions)
+ expect(data.levels).toEqual(levels.map(toDocId))
+ })
+
+ it('caches the map, once loaded', async () => {
+ const fieldDoc = { _id: simpleRandom(), title: simpleRandom() }
+ const dimensions = [
+ { _id: simpleRandom(), title: simpleRandom(), shortCode: 'R' },
+ { _id: simpleRandom(), title: simpleRandom(), shortCode: 'W' }
+ ]
+
+ const DimensionCollection = Dimension.collection()
+
+ stub(DimensionCollection, 'findOne', (_id) => {
+ return dimensions.find((byDocId(_id)))
+ })
+
+ const levels = [
+ { _id: simpleRandom() },
+ { _id: simpleRandom() }
+ ]
+
+ const mapData = {
+ viewElementsAdded: false,
+ dimensionsResolved: false,
+ dimensions: dimensions.map(toDocId),
+ levels: levels.map(toDocId),
+ entries: [{}]
+ }
+
+ let callCount = 0
+ mockCall((name, args, cb) => {
+ callCount++
+ mapData.viewElementsAdded = true
+ mapData.dimensionsResolved = true
+ setTimeout(() => cb(undefined, mapData))
+ })
+
+ const data1 = await loadMapData({ fieldDoc, loadUserData: null })
+ const data2 = await loadMapData({ fieldDoc, loadUserData: null })
+ expect(callCount).toEqual(1)
+ expect(data1).toEqual(data2)
+ })
+
+ it('adds additional rendering information to the entries', async () => {
+ const fieldDoc = { _id: simpleRandom(), title: simpleRandom() }
+ const dimensions = [
+ { _id: simpleRandom(), title: simpleRandom(), shortCode: 'R' },
+ { _id: simpleRandom(), title: simpleRandom(), shortCode: 'W' }
+ ]
+
+ const DimensionCollection = Dimension.collection()
+
+ stub(DimensionCollection, 'findOne', (_id) => {
+ return dimensions.find((byDocId(_id)))
+ })
+
+ const levels = [
+ { _id: simpleRandom() },
+ { _id: simpleRandom() }
+ ]
+
+ stub(MapIcons.collection(), 'findOne', () => ({
+ fieldId: fieldDoc._id,
+ icons: ['foo', 'bar']
+ }))
+
+ MapIcons.setField(fieldDoc._id)
+
+ const mapData = {
+ dimensions: dimensions.map(toDocId),
+ levels: levels.map(toDocId),
+ entries: [
+ {
+ type: 'stage'
+ },
+ {
+ type: 'stage'
+ },
+ {
+ type: 'milestone'
+ },
+ {
+ type: 'stage'
+ },
+ {
+ type: 'stage'
+ },
+ {
+ type: 'milestone'
+ }
+ ]
+ }
+ mockCall((name, args, cb) => setTimeout(() => cb(undefined, mapData)))
+
+ const { entries } = await loadMapData({ fieldDoc, loadUserData: null })
+
+ // first
+ expect(entries[0]).toEqual({
+ type: 'start',
+ entryKey: 'map-entry-0',
+ viewPosition: {
+ icon: 0,
+ current: 'center',
+ left: 'fill',
+ right: 'left2right-up'
+ }
+ })
+
+ // when next is a stage then
+ // we display a connector on the opposite
+ // side of the entry
+ expect(entries[1]).toEqual({
+ type: 'stage',
+ entryKey: 'map-entry-1',
+ label: 1,
+ viewPosition: {
+ icon: 1,
+ left: 'right2left',
+ current: 'right',
+ right: null
+ }
+ })
+
+ // when next is not stage then there
+ // are no connectors and no icon
+ expect(entries[2]).toEqual({
+ type: 'stage',
+ entryKey: 'map-entry-2',
+ label: 2,
+ viewPosition: {
+ icon: -1,
+ left: null,
+ current: 'left',
+ right: null
+ }
+ })
+
+ // milestones are always centered
+ expect(entries[3]).toEqual({
+ type: 'milestone',
+ entryKey: 'map-entry-3',
+ viewPosition: {
+ left: 'right2left-down',
+ current: 'center',
+ right: 'left2right-up'
+ }
+ })
+
+ // another stage
+ expect(entries[4]).toEqual({
+ type: 'stage',
+ entryKey: 'map-entry-4',
+ label: 3,
+ viewPosition: {
+ icon: 0, // index begins again at 0
+ left: 'right2left',
+ current: 'right',
+ right: null
+ }
+ })
+
+ // last
+ expect(entries[6]).toEqual({
+ type: 'finish',
+ entryKey: 'map-entry-6',
+ viewPosition: {
+ current: 'center',
+ left: 'right2left-down',
+ right: 'fill'
+ }
+ })
+ })
+ test.todo('loads the map data with user progress added, if given')
+ test.todo('updates the user data into the cached map')
+})
diff --git a/app/__tests__/startup/createSessionValidator.tests.js b/app/__tests__/startup/createSessionValidator.tests.js
new file mode 100644
index 00000000..f6c7b29b
--- /dev/null
+++ b/app/__tests__/startup/createSessionValidator.tests.js
@@ -0,0 +1,5 @@
+import { createSessionValidator } from '../../lib/startup/createSessionValidator'
+
+describe(createSessionValidator.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/startup/initAppSession.tests.js b/app/__tests__/startup/initAppSession.tests.js
new file mode 100644
index 00000000..e56af2d8
--- /dev/null
+++ b/app/__tests__/startup/initAppSession.tests.js
@@ -0,0 +1,5 @@
+import { initAppSession } from '../../lib/startup/initAppSession'
+
+describe(initAppSession.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/startup/initContexts.tests.js b/app/__tests__/startup/initContexts.tests.js
new file mode 100644
index 00000000..c124f773
--- /dev/null
+++ b/app/__tests__/startup/initContexts.tests.js
@@ -0,0 +1,5 @@
+import { initContexts } from '../../lib/startup/initContexts'
+
+describe(initContexts.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/startup/initExceptionHandling.tests.js b/app/__tests__/startup/initExceptionHandling.tests.js
new file mode 100644
index 00000000..36457266
--- /dev/null
+++ b/app/__tests__/startup/initExceptionHandling.tests.js
@@ -0,0 +1,5 @@
+import { initExceptionHandling } from '../../lib/startup/initExceptionHandling'
+
+describe(initExceptionHandling.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/startup/initSound.tests.js b/app/__tests__/startup/initSound.tests.js
new file mode 100644
index 00000000..a7a2e0b4
--- /dev/null
+++ b/app/__tests__/startup/initSound.tests.js
@@ -0,0 +1,5 @@
+import { initSound } from '../../lib/startup/initSound'
+
+describe(initSound.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/startup/initTts.tests.js b/app/__tests__/startup/initTts.tests.js
new file mode 100644
index 00000000..e8764bf5
--- /dev/null
+++ b/app/__tests__/startup/initTts.tests.js
@@ -0,0 +1,5 @@
+import { initTTs } from '../../lib/startup/initTTS'
+
+describe(initTTs.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/state/AppSession.tests.js b/app/__tests__/state/AppSession.tests.js
new file mode 100644
index 00000000..7cd9b5c6
--- /dev/null
+++ b/app/__tests__/state/AppSession.tests.js
@@ -0,0 +1,5 @@
+import { AppSession } from '../../lib/state/AppSession'
+
+describe(AppSession.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/styles/createStyleSheet.tests.js b/app/__tests__/styles/createStyleSheet.tests.js
new file mode 100644
index 00000000..b6be8c97
--- /dev/null
+++ b/app/__tests__/styles/createStyleSheet.tests.js
@@ -0,0 +1,5 @@
+import { createStyleSheet } from '../../lib/styles/createStyleSheet'
+
+describe(createStyleSheet.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/styles/makeTransparent.tests.js b/app/__tests__/styles/makeTransparent.tests.js
new file mode 100644
index 00000000..42a958b6
--- /dev/null
+++ b/app/__tests__/styles/makeTransparent.tests.js
@@ -0,0 +1,5 @@
+import { makeTransparent } from '../../lib/styles/makeTransparent'
+
+describe(makeTransparent.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/styles/mergeStyles.tests.js b/app/__tests__/styles/mergeStyles.tests.js
new file mode 100644
index 00000000..8de4c27a
--- /dev/null
+++ b/app/__tests__/styles/mergeStyles.tests.js
@@ -0,0 +1,5 @@
+import { mergeStyles } from '../../lib/styles/mergeStyles'
+
+describe(mergeStyles.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/tts/TTSSpeedConfig.tests.js b/app/__tests__/tts/TTSSpeedConfig.tests.js
new file mode 100644
index 00000000..2744703d
--- /dev/null
+++ b/app/__tests__/tts/TTSSpeedConfig.tests.js
@@ -0,0 +1,5 @@
+import { TTSSpeedConfig } from '../../lib/tts/TTSSpeedConfig'
+
+describe(TTSSpeedConfig.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/tts/TTSVoiceConfig.tests.js b/app/__tests__/tts/TTSVoiceConfig.tests.js
new file mode 100644
index 00000000..51a0a480
--- /dev/null
+++ b/app/__tests__/tts/TTSVoiceConfig.tests.js
@@ -0,0 +1,5 @@
+import { TTSVoiceConfig } from '../../lib/tts/TTSVoiceConfig'
+
+describe(TTSVoiceConfig.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/utils/array/byDocId.tests.js b/app/__tests__/utils/array/byDocId.tests.js
new file mode 100644
index 00000000..2f2d85f3
--- /dev/null
+++ b/app/__tests__/utils/array/byDocId.tests.js
@@ -0,0 +1,17 @@
+import { byDocId } from '../../../lib/utils/array/byDocId'
+
+describe(byDocId.name, function () {
+ it('helps to find the doc id in target', () => {
+ const _id = 'foo'
+ const matcher = byDocId(_id)
+ expect(matcher({ _id })).toBe(true)
+ expect(matcher({ _id: 'bar' })).toBe(false)
+ expect(matcher({})).toBe(false)
+ expect(matcher([])).toBe(false)
+ expect(matcher(new Date())).toBe(false)
+ expect(matcher()).toBe(false)
+
+ const filtered = [{ _id }, { _id }, { _id: 'bar' }, { _id }, { _id }].filter(matcher)
+ expect(filtered.length).toBe(4)
+ })
+})
diff --git a/app/__tests__/utils/array/byOrderedIds.tests.js b/app/__tests__/utils/array/byOrderedIds.tests.js
new file mode 100644
index 00000000..e1dbfa34
--- /dev/null
+++ b/app/__tests__/utils/array/byOrderedIds.tests.js
@@ -0,0 +1,33 @@
+import { byOrderedIds } from '../../../lib/utils/array/byOrderedIds'
+
+describe(byOrderedIds.name, function () {
+ it('throws if ids are not found in the given list of ids [empty list]', () => {
+ const sorter = byOrderedIds()
+ const a = { _id: 'foo' }
+ const b = { _id: 'bar' }
+ expect(() => sorter(a, b))
+ .toThrow('Expected foo and bar to not result in -1 and -1')
+ })
+ it('throws if ids are not found in the given list of ids [a not found]', () => {
+ const sorter = byOrderedIds(['bar', 'baz'])
+ const a = { _id: 'foo' }
+ const b = { _id: 'bar' }
+ expect(() => sorter(a, b))
+ .toThrow('Expected foo and bar to not result in -1 and 0')
+ })
+ it('throws if ids are not found in the given list of ids [b not found]', () => {
+ const sorter = byOrderedIds(['foo', 'baz'])
+ const a = { _id: 'foo' }
+ const b = { _id: 'bar' }
+ expect(() => sorter(a, b))
+ .toThrow('Expected foo and bar to not result in 0 and -1')
+ })
+ it('sorts by given ids', () => {
+ const sorter = byOrderedIds(['foo', 'bar', 'baz', 'moo'])
+ const sorted = [{ _id: 'moo' }, { _id: 'foo' }, { _id: 'baz' }, { _id: 'bar' }]
+ sorted.sort(sorter)
+ expect(sorted).toStrictEqual([
+ { _id: 'foo' }, { _id: 'bar' }, { _id: 'baz' }, { _id: 'moo' }
+ ])
+ })
+})
diff --git a/app/__tests__/utils/array/randomArrayElement.tests.js b/app/__tests__/utils/array/randomArrayElement.tests.js
new file mode 100644
index 00000000..bfe2a5ad
--- /dev/null
+++ b/app/__tests__/utils/array/randomArrayElement.tests.js
@@ -0,0 +1,32 @@
+import { randomArrayElement } from '../../../lib/utils/array/randomArrayElement'
+
+jest.retryTimes(1)
+
+describe(randomArrayElement.name, function () {
+ it('returns the first element in 0 or 1 length arrays', () => {
+ expect(randomArrayElement([])).toBe(undefined)
+ expect(randomArrayElement([0])).toBe(0)
+ })
+ it('throws if given value is no arary', () => {
+ [{}, () => {}, new Date(), 1, '1', false, undefined, null]
+ .forEach(value => {
+ expect(() => randomArrayElement(value))
+ .toThrow(`Expected array, got ${value}`)
+ })
+ })
+ it('returns a random element from an array', () => {
+ const input = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ const covered = new Set()
+ for (let i = 0; i < 1000; i++) {
+ const element = randomArrayElement(input)
+ covered.add(element)
+ const index = input.indexOf(element)
+ expect(index).toBeGreaterThan(-1)
+ }
+
+ // there might be the chance of failing,
+ // but it's super low!
+ // still we use retryTimes to cover this
+ expect(covered.size).toBe(input.length)
+ })
+})
diff --git a/app/__tests__/utils/array/toArrayIfNot.tests.js b/app/__tests__/utils/array/toArrayIfNot.tests.js
new file mode 100644
index 00000000..81e3475d
--- /dev/null
+++ b/app/__tests__/utils/array/toArrayIfNot.tests.js
@@ -0,0 +1,20 @@
+import { toArrayIfNot } from '../../../lib/utils/array/toArrayIfNot'
+
+describe(toArrayIfNot.name, function () {
+ it('returns an empty array if undefined is passed', () => {
+ expect(toArrayIfNot()).toStrictEqual([])
+ })
+ it('returns an array of a single non-array value/object', () => {
+ [() => {}, {}, 1, '1', false, true, 0.1 + 0.2, null]
+ .forEach(value => {
+ expect(toArrayIfNot(value))
+ .toStrictEqual([value])
+ })
+ })
+ it('returns the array if it is an array', () => {
+ [[() => {}], [{}], [1], ['1'], [false], [true], [0.1 + 0.2], [null]]
+ .forEach(array => {
+ expect(toArrayIfNot(array) === array).toBe(true)
+ })
+ })
+})
diff --git a/app/__tests__/utils/array/toDocId.tests.js b/app/__tests__/utils/array/toDocId.tests.js
new file mode 100644
index 00000000..27490832
--- /dev/null
+++ b/app/__tests__/utils/array/toDocId.tests.js
@@ -0,0 +1,8 @@
+import { toDocId } from '../../../lib/utils/array/toDocId'
+
+describe(toDocId.name, function () {
+ it('maps objects to their doc ids', () => {
+ expect([{ _id: 'foo' }, { _id: 'bar' }].map(toDocId))
+ .toStrictEqual(['foo', 'bar'])
+ })
+})
diff --git a/app/__tests__/utils/createTimesPromise.tests.js b/app/__tests__/utils/createTimesPromise.tests.js
new file mode 100644
index 00000000..a9507f70
--- /dev/null
+++ b/app/__tests__/utils/createTimesPromise.tests.js
@@ -0,0 +1,43 @@
+import { createTimedPromise } from '../../lib/utils/createTimedPromise'
+
+const createPromise = (timeout, message = 'foo') => new Promise(resolve => {
+ const timer = setTimeout(() => {
+ clearTimeout(timer)
+ resolve(message)
+ }, timeout)
+})
+
+describe(createTimedPromise.name, function () {
+ beforeAll(() => {
+ jest.useFakeTimers({ advanceTimers: true })
+ })
+ it('resolves to the promise if it resolves faster', async () => {
+ const myPromise = createPromise(250, 'foo1')
+ const value = await createTimedPromise(myPromise)
+ expect(value).toEqual('foo1')
+ })
+ it('resolves to the fallback promise if it resolves not fast enough', async () => {
+ const myPromise = createPromise(1250)
+ const message = 'bar1'
+ const timeout = 1000
+ const options = { message, timeout }
+ const value = await createTimedPromise(myPromise, options)
+ expect(value).toEqual('bar1')
+ })
+ it('rejects the fallback promise if it resolves not fast enough and displays a custom message', () => {
+ const myPromise1 = createPromise(1250)
+ const message = 'bar2'
+ const timeout = 1000
+ const options = { message, timeout, throwIfTimedOut: true }
+ const race = createTimedPromise(myPromise1, options)
+ expect(race).rejects.toThrow('bar2')
+ })
+ it('rejects the fallback promise if it resolves not fast enough and displays custom details', () => {
+ const myPromise2 = createPromise(1250)
+ const options = { throwIfTimedOut: true, details: { bar: 'baz1' }, timeout: 1000 }
+ const race = createTimedPromise(myPromise2, options)
+ const target = expect(race).rejects
+ target.toThrow('promise.timedOut')
+ target.toHaveProperty('details', options.details)
+ })
+})
diff --git a/app/__tests__/utils/isOS.tests.js b/app/__tests__/utils/isOS.tests.js
new file mode 100644
index 00000000..b8163c91
--- /dev/null
+++ b/app/__tests__/utils/isOS.tests.js
@@ -0,0 +1,5 @@
+import { isIOS } from '../../lib/utils/isIOS'
+
+describe(isIOS.name, function () {
+ test.todo('it is not impl')
+})
diff --git a/app/__tests__/utils/math/average.tests.js b/app/__tests__/utils/math/average.tests.js
new file mode 100644
index 00000000..6d8860f2
--- /dev/null
+++ b/app/__tests__/utils/math/average.tests.js
@@ -0,0 +1,16 @@
+import { average } from '../../../lib/utils/math/average'
+
+describe(average.name, function () {
+ it('computes the average between two values', () => {
+ [
+ [0, 0, 0],
+ [0, 1, 0],
+ [1, 2, 0.5],
+ [1, 3, 1 / 3],
+ [-1, 3, 0],
+ [-1, -1, 0]
+ ].forEach(([sum, max, expected]) => {
+ expect(average(sum, max)).toBe(expected)
+ })
+ })
+})
diff --git a/app/__tests__/utils/math/getPositionOnCircle.tests.js b/app/__tests__/utils/math/getPositionOnCircle.tests.js
new file mode 100644
index 00000000..c93bfc6e
--- /dev/null
+++ b/app/__tests__/utils/math/getPositionOnCircle.tests.js
@@ -0,0 +1,21 @@
+import { getPositionOnCircle } from '../../../lib/utils/trigonometry/getPositionOnCircle'
+
+describe(getPositionOnCircle.name, function () {
+ it('returns the n equally distributed positions on a circle by given radius', () => {
+ const positions = getPositionOnCircle({ n: 3, radius: 1, precision: 5 })
+ expect(positions).toStrictEqual([
+ {
+ x: 2,
+ y: 1
+ },
+ {
+ x: 0.5,
+ y: 1.866
+ },
+ {
+ x: 0.5,
+ y: 0.13397
+ }
+ ])
+ })
+})
diff --git a/app/__tests__/utils/math/randomInclusive.tests.js b/app/__tests__/utils/math/randomInclusive.tests.js
new file mode 100644
index 00000000..ba5685d6
--- /dev/null
+++ b/app/__tests__/utils/math/randomInclusive.tests.js
@@ -0,0 +1,14 @@
+import { randomIntInclusive } from '../../../lib/utils/math/randomIntInclusive'
+import { getInvalidIntegers } from '../../../__testHelpers__/getInvalidIntegers'
+
+describe(randomIntInclusive.name, function () {
+ it('throws if one or both numbers are not valid ints', () => {
+ const invalid = getInvalidIntegers()
+ invalid.forEach(value => {
+ expect(() => randomIntInclusive(value, 1))
+ .toThrow(`Expected safe integers, got ${value} and 1`)
+ expect(() => randomIntInclusive(1, value))
+ .toThrow(`Expected safe integers, got 1 and ${value}`)
+ })
+ })
+})
diff --git a/app/__tests__/utils/number/isSafeInteger.tests.js b/app/__tests__/utils/number/isSafeInteger.tests.js
new file mode 100644
index 00000000..787de2a0
--- /dev/null
+++ b/app/__tests__/utils/number/isSafeInteger.tests.js
@@ -0,0 +1,17 @@
+import { isSafeInteger } from '../../../lib/utils/number/isSafeInteger'
+import { getInvalidIntegers } from '../../../__testHelpers__/getInvalidIntegers'
+
+describe(isSafeInteger.name, function () {
+ it('returns false for invalid integers', () => {
+ const invalid = getInvalidIntegers()
+ invalid.forEach(value => {
+ expect(isSafeInteger(value)).toBe(false)
+ })
+ })
+ it('returns true on valid integers', () => {
+ const valid = [1, 2, 3, 0, -1, -2, 1.0, -3.0]
+ valid.forEach(value => {
+ expect(isSafeInteger(value)).toBe(true)
+ })
+ })
+})
diff --git a/app/__tests__/utils/number/isValidNumber.tests.js b/app/__tests__/utils/number/isValidNumber.tests.js
new file mode 100644
index 00000000..fdc12bea
--- /dev/null
+++ b/app/__tests__/utils/number/isValidNumber.tests.js
@@ -0,0 +1,11 @@
+import { isValidNumber } from '../../../lib/utils/number/isValidNumber'
+import { getInvalidNumbers } from '../../../__testHelpers__/getInvalidNumbers'
+
+describe(isValidNumber.name, function () {
+ it('returns false on invalid integers', () => {
+ const invalid = getInvalidNumbers()
+ invalid.forEach(value => {
+ expect(isValidNumber(value)).toBe(false)
+ })
+ })
+})
diff --git a/app/__tests__/utils/number/toInteger.tests.js b/app/__tests__/utils/number/toInteger.tests.js
new file mode 100644
index 00000000..d0352500
--- /dev/null
+++ b/app/__tests__/utils/number/toInteger.tests.js
@@ -0,0 +1,16 @@
+import { toInteger } from '../../../lib/utils/number/toInteger'
+
+describe(toInteger.name, function () {
+ it('parses string to an integer', () => {
+ [
+ ['1', 1],
+ ['1.0', 1],
+ ['1.1', 1],
+ ['-1', -1],
+ ['-1.0', -1],
+ ['-1.1', -1]
+ ].forEach(([n, expected]) => {
+ expect(toInteger(n)).toBe(expected)
+ })
+ })
+})
diff --git a/app/__tests__/utils/object/clearObject.tests.js b/app/__tests__/utils/object/clearObject.tests.js
new file mode 100644
index 00000000..15c60415
--- /dev/null
+++ b/app/__tests__/utils/object/clearObject.tests.js
@@ -0,0 +1,17 @@
+import { clearObject } from '../../../lib/utils/object/clearObject'
+
+describe(clearObject.name, function () {
+ it('removes all own properties of an object', () => {
+ const obj = { foo: 'bar' }
+ clearObject(obj)
+ expect(obj).toStrictEqual({})
+ expect(typeof obj.toString).toBe('function')
+ })
+ it('throws if obj is not an object', () => {
+ [[], null, undefined, 1, 1.2, '1', false, true, () => {}]
+ .forEach(value => {
+ expect(() => clearObject(value))
+ .toThrow(`Expected objected, got ${typeof value}`)
+ })
+ })
+})
diff --git a/app/__tests__/utils/object/hasOwnProps.tests.js b/app/__tests__/utils/object/hasOwnProps.tests.js
new file mode 100644
index 00000000..0fb6385b
--- /dev/null
+++ b/app/__tests__/utils/object/hasOwnProps.tests.js
@@ -0,0 +1,14 @@
+import { hasOwnProp } from '../../../lib/utils/object/hasOwnProp'
+
+describe(hasOwnProp.name, function () {
+ it('returns true only for own props', () => {
+ const obj = { foo: 'bar' }
+ expect(hasOwnProp(obj, 'foo')).toBe(true)
+
+ class A {
+ foo () { return 1 }
+ }
+ const a = new A()
+ expect(hasOwnProp(a, 'foo')).toBe(false)
+ })
+})
diff --git a/app/__tests__/utils/object/isDefined.tests.js b/app/__tests__/utils/object/isDefined.tests.js
new file mode 100644
index 00000000..063931f8
--- /dev/null
+++ b/app/__tests__/utils/object/isDefined.tests.js
@@ -0,0 +1,10 @@
+import { isDefined } from '../../../lib/utils/object/isDefined'
+
+describe(isDefined.name, function () {
+ it('returns only true if something is not undefined and not null', () => {
+ [undefined, null].forEach(val => expect(isDefined(val)).toBe(false))
+
+ ;[true, false, 'a', 1, 0, '0', () => {}, []]
+ .forEach(val => expect(isDefined(val)).toBe(true))
+ })
+})
diff --git a/app/__tests__/utils/text/createSimpleTokenizer.tests.js b/app/__tests__/utils/text/createSimpleTokenizer.tests.js
new file mode 100644
index 00000000..ac580866
--- /dev/null
+++ b/app/__tests__/utils/text/createSimpleTokenizer.tests.js
@@ -0,0 +1,12 @@
+import { createSimpleTokenizer } from '../../../lib/utils/text/createSimpleTokenizer'
+
+describe(createSimpleTokenizer.name, function () {
+ it('returns an empty array if input is not a string of length > 0', () => {
+ const tokenize = createSimpleTokenizer('[', ']')
+
+ ;[null, undefined, false, true, '', 0, 1, {}, [], () => {}]
+ .forEach(val => {
+ expect(tokenize(val)).toStrictEqual([])
+ })
+ })
+})
diff --git a/app/__tests__/utils/text/isWord.tests.js b/app/__tests__/utils/text/isWord.tests.js
new file mode 100644
index 00000000..f784bcb0
--- /dev/null
+++ b/app/__tests__/utils/text/isWord.tests.js
@@ -0,0 +1,10 @@
+import { isWord } from '../../../lib/utils/text/isWord'
+
+describe(isWord.name, function () {
+ it('returns only true of smething is a string with length > 0', () => {
+ [null, undefined, false, true, '', 0, 1, {}, [], () => {}]
+ .forEach(val => expect(isWord(val)).toBe(false))
+ ;['1', '0', ' ', '\n', '\t', 'foo']
+ .forEach(val => expect(isWord(val)).toBe(true))
+ })
+})
diff --git a/app/babel.config.js b/app/babel.config.js
index 9d89e131..d799d652 100644
--- a/app/babel.config.js
+++ b/app/babel.config.js
@@ -1,6 +1,6 @@
module.exports = function (api) {
- api.cache(true);
+ api.cache(true)
return {
- presets: ['babel-preset-expo'],
- };
-};
+ presets: ['babel-preset-expo']
+ }
+}
diff --git a/app/build.js b/app/build.js
index 1c3a939b..2d136528 100644
--- a/app/build.js
+++ b/app/build.js
@@ -2,7 +2,6 @@ const fs = require('node:fs')
const path = require('node:path')
const { spawnSync } = require('node:child_process')
-
let BUILD_PROFILE = 'preview'
let BUILD_LOCAL = false
@@ -17,7 +16,7 @@ process.argv
})
const SETTINGS_PATH = path.join(process.cwd(), ((type) => {
- switch(type) {
+ switch (type) {
case 'staging':
return '.deploy/settings.staging.json'
case 'production':
@@ -29,7 +28,7 @@ const SETTINGS_PATH = path.join(process.cwd(), ((type) => {
const SETTINGS_DESTINATION = path.resolve('settings/settings.json')
const ORIGINAL_SETTINGS = fs.readFileSync(SETTINGS_DESTINATION)
-const settings_src = fs.readFileSync(SETTINGS_PATH)
+const SETTINGS_SRC = fs.readFileSync(SETTINGS_PATH)
const recoverSettings = () => {
fs.writeFileSync(SETTINGS_DESTINATION, ORIGINAL_SETTINGS)
@@ -38,18 +37,19 @@ const recoverSettings = () => {
const attempt = (fn) => {
try {
fn()
- } catch (e) {
+ }
+ catch (e) {
console.error(e)
recoverSettings()
process.exit(1)
}
}
-attempt(() => fs.writeFileSync(SETTINGS_DESTINATION, settings_src))
+attempt(() => fs.writeFileSync(SETTINGS_DESTINATION, SETTINGS_SRC))
attempt(() => {
const args = ['eas', 'build', '--platform', 'android', '--profile', BUILD_PROFILE]
if (BUILD_LOCAL) args.push('--local')
- spawnSync('npx', args, { stdio: 'inherit' });
+ spawnSync('npx', args, { stdio: 'inherit' })
recoverSettings()
-})
\ No newline at end of file
+})
diff --git a/app/jest.config.js b/app/jest.config.js
new file mode 100644
index 00000000..7e91d135
--- /dev/null
+++ b/app/jest.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+ preset: 'jest-expo',
+ transformIgnorePatterns: [
+ 'node_modules/(?!(jest-)?react-native|@meteorrn|@react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)'
+ ],
+ collectCoverage: true,
+ coverageDirectory: '.coverage',
+ coverageReporters: ['html', 'text'],
+ setupFiles: ['./jestSetup.js']
+}
diff --git a/app/jestSetup.js b/app/jestSetup.js
new file mode 100644
index 00000000..4135fea8
--- /dev/null
+++ b/app/jestSetup.js
@@ -0,0 +1,3 @@
+jest.mock('@react-native-async-storage/async-storage', () =>
+ require('@react-native-async-storage/async-storage/jest/async-storage-mock')
+)
diff --git a/app/lib/components/BackButton.js b/app/lib/components/BackButton.js
index 7b71a7bc..57fabd10 100644
--- a/app/lib/components/BackButton.js
+++ b/app/lib/components/BackButton.js
@@ -28,7 +28,7 @@ export const BackButton = (props) => {
hitSlop={10}
android_ripple={rippleConfig}
>
-
+
)
}
diff --git a/app/lib/components/animated/DashedLine.js b/app/lib/components/animated/DashedLine.js
index 28df1577..080fe2dd 100644
--- a/app/lib/components/animated/DashedLine.js
+++ b/app/lib/components/animated/DashedLine.js
@@ -9,14 +9,14 @@ export const DashedLine = props => {
useEffect(() => {
Animated.loop(
- Animated.timing(offset, {
- toValue: props.invert ? 0 : 100,
- duration: 5000,
- useNativeDriver: true,
- isInteraction: false,
- easing: Easing.linear
- })).start()
- }, [offset]);
+ Animated.timing(offset, {
+ toValue: props.invert ? 0 : 100,
+ duration: 5000,
+ useNativeDriver: true,
+ isInteraction: false,
+ easing: Easing.linear
+ })).start()
+ }, [offset])
return (
{
- )
+ )
: ( )
}
diff --git a/app/lib/contexts/MapIcons.js b/app/lib/contexts/MapIcons.js
index 1c9e99a1..12753dc0 100644
--- a/app/lib/contexts/MapIcons.js
+++ b/app/lib/contexts/MapIcons.js
@@ -3,6 +3,8 @@ import { Colors } from '../constants/Colors'
import { createContextStorage } from './createContextStorage'
import Icon from '@expo/vector-icons/FontAwesome6'
import { collectionNotInitialized } from './collectionNotInitialized'
+import { createStyleSheet } from '../styles/createStyleSheet'
+import { View } from 'react-native'
export const MapIcons = {
name: 'mapIcons'
@@ -48,13 +50,24 @@ MapIcons.render = (index) => {
const name = internal.icons[index]
return (
+
+
)
}
-const MemoIcon = React.memo(Icon)
+const styles = createStyleSheet({
+ container: {
+ alignItems: 'center',
+ justifyContent: 'center'
+ },
+ icon: {
+ flex: 0
+ }
+})
diff --git a/app/lib/hooks/useConnection.js b/app/lib/hooks/useConnection.js
index db4f551c..efe1f717 100644
--- a/app/lib/hooks/useConnection.js
+++ b/app/lib/hooks/useConnection.js
@@ -101,7 +101,7 @@ export const useConnection = () => {
return {
connected,
www,
- backend,
+ backend
}
}
diff --git a/app/lib/hooks/useDevelopment.js b/app/lib/hooks/useDevelopment.js
index 181ce177..1bd15c11 100644
--- a/app/lib/hooks/useDevelopment.js
+++ b/app/lib/hooks/useDevelopment.js
@@ -9,7 +9,7 @@ export const useDevelopment = () => {
useEffect(() => {
setIsDevelopment(Config.isDevelopment)
setIsDeveloperRelease(Config.isDeveloperRelease())
- setIsDeveloperRelease(Config.isTest())
+ setIsTest(Config.isTest())
}, [])
return { isDevelopment, isDeveloperRelease, isTest }
diff --git a/app/lib/i18n.js b/app/lib/i18n.js
index fe4fd845..a1a7517a 100644
--- a/app/lib/i18n.js
+++ b/app/lib/i18n.js
@@ -25,7 +25,10 @@ const resources = {
continue: 'Continue'
},
connecting: {
- title: 'You are offline. I\'m trying to connect.'
+ title: 'You are offline. I\'m trying to connect.',
+ done: 'You are connected again! 🎉',
+ backend: 'You are currently not connected to the lea-system.',
+ www: 'You have no internet connection, please check it.'
},
actions: {
back: 'Back',
@@ -179,6 +182,7 @@ const resources = {
continue: 'Weiter'
},
connecting: {
+ title: 'Verbinde mit dem lea-System',
done: 'Du bist wieder verbunden! 🎉',
backend: 'Du bist aktuell nicht mit dem lea-System verbunden. ',
www: 'Du bist aktuell nicht mit dem Internet verbunden. Bitte prüfe deine Internet\u00ADverbindung.'
diff --git a/app/lib/items/cloze/createScoringSummaryForInput.js b/app/lib/items/cloze/createScoringSummaryForInput.js
index be97719b..c9146e29 100644
--- a/app/lib/items/cloze/createScoringSummaryForInput.js
+++ b/app/lib/items/cloze/createScoringSummaryForInput.js
@@ -25,7 +25,7 @@ export const createScoringSummaryForInput = ({ itemIndex, actual, entries }) =>
index: itemIndex,
score: 0, // computed average
color: undefined,
- actual: actual,
+ actual,
entries: []
}
diff --git a/app/lib/items/connect/ConnectItemRenderer.js b/app/lib/items/connect/ConnectItemRenderer.js
index 87308f9e..a9e79aff 100644
--- a/app/lib/items/connect/ConnectItemRenderer.js
+++ b/app/lib/items/connect/ConnectItemRenderer.js
@@ -10,7 +10,6 @@ import { Svg, Circle, G } from 'react-native-svg'
import { UndefinedScore } from '../../scoring/UndefinedScore'
import { clearObject } from '../../utils/object/clearObject'
import { ImageRenderer } from '../../components/renderer/media/ImageRenderer'
-import { Layout } from '../../constants/Layout'
import { Log } from '../../infrastructure/Log'
import { DashedLine } from '../../components/animated/DashedLine'
diff --git a/app/lib/screens/auth/RegistrationScreen.js b/app/lib/screens/auth/RegistrationScreen.js
index 6ec44058..c2dc652b 100644
--- a/app/lib/screens/auth/RegistrationScreen.js
+++ b/app/lib/screens/auth/RegistrationScreen.js
@@ -9,7 +9,7 @@ import { createStyleSheet } from '../../styles/createStyleSheet'
import { Layout } from '../../constants/Layout'
import { Loading } from '../../components/Loading'
import { InteractionGraph } from '../../infrastructure/log/InteractionGraph'
-import { useRoute } from '@react-navigation/native';
+import { useRoute } from '@react-navigation/native'
/**
* Screen for registering a new user.
* This screen should automatically run without further actions required.
diff --git a/app/lib/screens/map/components/Milestone.js b/app/lib/screens/map/components/Milestone.js
index 71bc7944..e9217e02 100644
--- a/app/lib/screens/map/components/Milestone.js
+++ b/app/lib/screens/map/components/Milestone.js
@@ -54,7 +54,7 @@ const getStars = (value) => {
const xOffsetLeft = ((value - 1) * 9) / 2
for (let i = 0; i < value; i++) {
- stars[i] = ( )
+ stars[i] = ( )
}
return stars
diff --git a/app/lib/screens/map/loadMapIcons.js b/app/lib/screens/map/loadMapIcons.js
deleted file mode 100644
index 4623206c..00000000
--- a/app/lib/screens/map/loadMapIcons.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { MapIcons } from '../../contexts/MapIcons'
-import { Anchor } from './icons/Anchor'
-import { Cogwheels } from './icons/Cogwheels'
-import { Building } from './icons/Building'
-import { Wrench } from './icons/Wrench'
-import { Astronaut } from './icons/Astronaut'
-import { Truck } from './icons/Truck'
-
-export const loadMapIcons = () => {
- if (MapIcons.size() > 0) {
- return false
- }
- MapIcons.register(Anchor)
- MapIcons.register(Cogwheels)
- MapIcons.register(Building)
- MapIcons.register(Wrench)
- MapIcons.register(Astronaut)
- MapIcons.register(Truck)
- return true
-}
diff --git a/app/lib/screens/map/loadProgressData.js b/app/lib/screens/map/loadProgressData.js
index 2fec326c..59df7303 100644
--- a/app/lib/screens/map/loadProgressData.js
+++ b/app/lib/screens/map/loadProgressData.js
@@ -6,7 +6,6 @@ const debug = Log.create('loadProgressDoc', 'debug')
export const loadProgressDoc = async (fieldId) => {
debug('for', { fieldId })
-
const progressDoc = await callMeteor({
name: Config.methods.getProgress,
args: { fieldId },
diff --git a/app/lib/screens/unit/renderer/InstructionsGraphicsRenderer.js b/app/lib/screens/unit/renderer/InstructionsGraphicsRenderer.js
index c4e56372..90c6f169 100644
--- a/app/lib/screens/unit/renderer/InstructionsGraphicsRenderer.js
+++ b/app/lib/screens/unit/renderer/InstructionsGraphicsRenderer.js
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react'
+import React, { useCallback, useEffect, useState } from 'react'
import { Vibration, View } from 'react-native'
import { InstructionAnimations } from '../instructions/InstructionAnimations'
import { useTts, TTSengine } from '../../../components/Tts'
@@ -81,6 +81,6 @@ const styles = createStyleSheet({
flex: 1,
paddingLeft: 20,
paddingTop: 15,
- paddingBottom: 15,
+ paddingBottom: 15
}
})
diff --git a/app/package-lock.json b/app/package-lock.json
index e8a87ce3..a4c1406a 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -50,12 +50,20 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
+ "@testing-library/react-native": "^12.6.1",
"@types/jest": "^29.5.12",
"@types/react": "~18.2.45",
"@types/react-test-renderer": "^18.0.7",
+ "eslint": "^8.57.0",
+ "eslint-config-expo": "^7.1.2",
+ "eslint-config-standard": "^17.1.0",
+ "eslint-plugin-jest": "^28.8.2",
+ "eslint-plugin-n": "^17.10.2",
+ "eslint-plugin-promise": "^7.1.0",
"jest": "^29.2.1",
"jest-expo": "~51.0.3",
"react-test-renderer": "18.2.0",
+ "sinon": "^18.0.0",
"typescript": "~5.3.3"
}
},
@@ -1498,6 +1506,154 @@
"node": ">=0.8.0"
}
},
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
+ "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/@eslint/eslintrc/node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
"node_modules/@expo/bunyan": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.1.tgz",
@@ -2663,6 +2819,41 @@
"@hapi/hoek": "^9.0.0"
}
},
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.14",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.2",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true
+ },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -3594,6 +3785,15 @@
"node": ">= 8"
}
},
+ "node_modules/@nolyfill/is-core-module": {
+ "version": "1.0.39",
+ "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
+ "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.4.0"
+ }
+ },
"node_modules/@npmcli/fs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz",
@@ -5773,6 +5973,12 @@
"node": ">=10"
}
},
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true
+ },
"node_modules/@segment/loosely-validate-event": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz",
@@ -5821,6 +6027,54 @@
"@sinonjs/commons": "^3.0.0"
}
},
+ "node_modules/@sinonjs/samsam": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz",
+ "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^2.0.0",
+ "lodash.get": "^4.4.2",
+ "type-detect": "^4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
+ "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/text-encoding": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz",
+ "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==",
+ "dev": true
+ },
+ "node_modules/@testing-library/react-native": {
+ "version": "12.6.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-12.6.1.tgz",
+ "integrity": "sha512-ykXKeQEkqJ/lk0dqGfEspODjV5skmUh/6vVtyGZQsneFrcqGZJrO4uGhmChICLbnEKkstxXkjXeyoB7z8k9InQ==",
+ "dev": true,
+ "dependencies": {
+ "jest-matcher-utils": "^29.7.0",
+ "pretty-format": "^29.7.0",
+ "redent": "^3.0.0"
+ },
+ "peerDependencies": {
+ "jest": ">=28.0.0",
+ "react": ">=16.8.0",
+ "react-native": ">=0.59",
+ "react-test-renderer": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "jest": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -5927,6 +6181,12 @@
"parse5": "^7.0.0"
}
},
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true
+ },
"node_modules/@types/node": {
"version": "18.19.47",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.47.tgz",
@@ -6007,102 +6267,338 @@
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="
},
- "node_modules/@urql/core": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz",
- "integrity": "sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==",
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
+ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
+ "dev": true,
"dependencies": {
- "@graphql-typed-document-node/core": "^3.1.0",
- "wonka": "^4.0.14"
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/type-utils": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
- "node_modules/@urql/exchange-retry": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-0.3.0.tgz",
- "integrity": "sha512-hHqer2mcdVC0eYnVNbWyi28AlGOPb2vjH3lP3/Bc8Lc8BjhMsDwFMm7WhoP5C1+cfbr/QJ6Er3H/L08wznXxfg==",
+ "node_modules/@typescript-eslint/parser": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
+ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
+ "dev": true,
"dependencies": {
- "@urql/core": ">=2.3.1",
- "wonka": "^4.0.14"
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4"
},
- "peerDependencies": {
- "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
- }
- },
- "node_modules/@xmldom/xmldom": {
- "version": "0.7.13",
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz",
- "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==",
"engines": {
- "node": ">=10.0.0"
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
- "node_modules/abab": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
- "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
- "deprecated": "Use your platform's native atob() and btoa() methods instead",
- "dev": true
- },
- "node_modules/abort-controller": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
- "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
+ "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
+ "dev": true,
"dependencies": {
- "event-target-shim": "^5.0.0"
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0"
},
"engines": {
- "node": ">=6.5"
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
+ "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
+ "dev": true,
"dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.3.0"
},
"engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/acorn": {
- "version": "8.12.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
- "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
- "bin": {
- "acorn": "bin/acorn"
+ "node": "^18.18.0 || >=20.0.0"
},
- "engines": {
- "node": ">=0.4.0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
- "node_modules/acorn-globals": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
- "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "node_modules/@typescript-eslint/types": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
+ "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
"dev": true,
- "dependencies": {
- "acorn": "^8.1.0",
- "acorn-walk": "^8.0.2"
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/acorn-walk": {
- "version": "8.3.3",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
- "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
+ "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
"dev": true,
"dependencies": {
- "acorn": "^8.11.0"
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^1.3.0"
},
"engines": {
- "node": ">=0.4.0"
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
- "node_modules/agent-base": {
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
+ "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
+ "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true
+ },
+ "node_modules/@urql/core": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz",
+ "integrity": "sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==",
+ "dependencies": {
+ "@graphql-typed-document-node/core": "^3.1.0",
+ "wonka": "^4.0.14"
+ },
+ "peerDependencies": {
+ "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
+ }
+ },
+ "node_modules/@urql/exchange-retry": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-0.3.0.tgz",
+ "integrity": "sha512-hHqer2mcdVC0eYnVNbWyi28AlGOPb2vjH3lP3/Bc8Lc8BjhMsDwFMm7WhoP5C1+cfbr/QJ6Er3H/L08wznXxfg==",
+ "dependencies": {
+ "@urql/core": ">=2.3.1",
+ "wonka": "^4.0.14"
+ },
+ "peerDependencies": {
+ "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
+ }
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.7.13",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz",
+ "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/abab": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
+ "deprecated": "Use your platform's native atob() and btoa() methods instead",
+ "dev": true
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.12.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
+ "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-globals": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+ "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.1.0",
+ "acorn-walk": "^8.0.2"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.3",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
+ "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
@@ -6273,6 +6769,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/array-includes": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
+ "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -6281,6 +6797,98 @@
"node": ">=8"
}
},
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz",
+ "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
+ "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz",
+ "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/arraybuffer.prototype.slice": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz",
@@ -7695,6 +8303,12 @@
"node": ">=4.0.0"
}
},
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -7845,6 +8459,15 @@
"node": ">=8"
}
},
+ "node_modules/diff": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/diff-sequences": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
@@ -7865,10 +8488,22 @@
"node": ">=8"
}
},
- "node_modules/dom-serializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
- "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
@@ -8007,6 +8642,19 @@
"once": "^1.4.0"
}
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
+ "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -8148,6 +8796,31 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-iterator-helpers": {
+ "version": "1.0.19",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz",
+ "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.3",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "globalthis": "^1.0.3",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.0.3",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.7",
+ "iterator.prototype": "^1.1.2",
+ "safe-array-concat": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-object-atoms": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
@@ -8159,52 +8832,755 @@
"node": ">= 0.4"
}
},
- "node_modules/es-set-tostringtag": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
- "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
+ "node_modules/es-set-tostringtag": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
+ "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
+ "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/escodegen/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+ "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.0",
+ "@humanwhocodes/config-array": "^0.11.14",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-compat-utils": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
+ "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
+ }
+ },
+ "node_modules/eslint-compat-utils/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-config-expo": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-7.1.2.tgz",
+ "integrity": "sha512-WxrDVNklN43Op0v3fglQfzL2bC7vqacUq9oVwJcGCUEDzdM7kGOR6pfEJiz3i3dQv3cFjHtct0CFEExep5c/dA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "^7.4.0",
+ "@typescript-eslint/parser": "^7.4.0",
+ "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-plugin-expo": "^0.0.1",
+ "eslint-plugin-import": "^2.29.1",
+ "eslint-plugin-react": "^7.34.0",
+ "eslint-plugin-react-hooks": "^4.6.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=8.10"
+ }
+ },
+ "node_modules/eslint-config-standard": {
+ "version": "17.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
+ "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^8.0.1",
+ "eslint-plugin-import": "^2.25.2",
+ "eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
+ "eslint-plugin-promise": "^6.0.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-typescript": {
+ "version": "3.6.3",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz",
+ "integrity": "sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==",
+ "dev": true,
+ "dependencies": {
+ "@nolyfill/is-core-module": "1.0.39",
+ "debug": "^4.3.5",
+ "enhanced-resolve": "^5.15.0",
+ "eslint-module-utils": "^2.8.1",
+ "fast-glob": "^3.3.2",
+ "get-tsconfig": "^4.7.5",
+ "is-bun-module": "^1.0.2",
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts"
+ },
+ "peerDependencies": {
+ "eslint": "*",
+ "eslint-plugin-import": "*",
+ "eslint-plugin-import-x": "*"
+ },
+ "peerDependenciesMeta": {
+ "eslint-plugin-import": {
+ "optional": true
+ },
+ "eslint-plugin-import-x": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.9.0.tgz",
+ "integrity": "sha512-McVbYmwA3NEKwRQY5g4aWMdcZE5xZxV8i8l7CqJSrameuGSQJtSWaL/LxTEzSKKaCcOhlpDR8XEfYXWPrdo/ZQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-es-x": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz",
+ "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/ota-meshi",
+ "https://opencollective.com/eslint"
+ ],
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.1.2",
+ "@eslint-community/regexpp": "^4.11.0",
+ "eslint-compat-utils": "^0.5.1"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=8"
+ }
+ },
+ "node_modules/eslint-plugin-expo": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-expo/-/eslint-plugin-expo-0.0.1.tgz",
+ "integrity": "sha512-dNri81vunJ3T+N1YWWxjLU6ux6KiukwZ4ECXCOPp8hG7M4kuvPAb9YQSIM63AT0pbtfYH/a6htikhaQcRPjhRA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "^7.2.0",
+ "@typescript-eslint/utils": "^7.2.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=8"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz",
+ "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==",
+ "dev": true,
+ "dependencies": {
+ "@rtsao/scc": "^1.1.0",
+ "array-includes": "^3.1.8",
+ "array.prototype.findlastindex": "^1.2.5",
+ "array.prototype.flat": "^1.3.2",
+ "array.prototype.flatmap": "^1.3.2",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.9.0",
+ "hasown": "^2.0.2",
+ "is-core-module": "^2.15.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "object.groupby": "^1.0.3",
+ "object.values": "^1.2.0",
+ "semver": "^6.3.1",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-jest": {
+ "version": "28.8.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.2.tgz",
+ "integrity": "sha512-mC3OyklHmS5i7wYU1rGId9EnxRI8TVlnFG56AE+8U9iRy6zwaNygZR+DsdZuCL0gRG0wVeyzq+uWcPt6yJrrMA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "engines": {
+ "node": "^16.10.0 || ^18.12.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0",
+ "jest": "*"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ },
+ "jest": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-n": {
+ "version": "17.10.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.2.tgz",
+ "integrity": "sha512-e+s4eAf5NtJaxPhTNu3qMO0Iz40WANS93w9LQgYcvuljgvDmWi/a3rh+OrNyMHeng6aOWGJO0rCg5lH4zi8yTw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "enhanced-resolve": "^5.17.0",
+ "eslint-plugin-es-x": "^7.5.0",
+ "get-tsconfig": "^4.7.0",
+ "globals": "^15.8.0",
+ "ignore": "^5.2.4",
+ "minimatch": "^9.0.5",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": ">=8.23.0"
+ }
+ },
+ "node_modules/eslint-plugin-n/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-n/node_modules/globals": {
+ "version": "15.9.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz",
+ "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-plugin-n/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/eslint-plugin-n/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-plugin-promise": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.1.0.tgz",
+ "integrity": "sha512-8trNmPxdAy3W620WKDpaS65NlM5yAumod6XeC4LOb+jxlkG4IVcp68c6dXY2ev+uT4U1PtG57YDV6EGAXN0GbQ==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.35.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.1.tgz",
+ "integrity": "sha512-B5ok2JgbaaWn/zXbKCGgKDNL2tsID3Pd/c/yvjcpsd9HQDwyYc/TQv3AZMmOvrJgCs3AnYNUHRCQEMMQAYJ7Yg==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.8",
+ "array.prototype.findlast": "^1.2.5",
+ "array.prototype.flatmap": "^1.3.2",
+ "array.prototype.tosorted": "^1.1.4",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.0.19",
+ "estraverse": "^5.3.0",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.8",
+ "object.fromentries": "^2.0.8",
+ "object.values": "^1.2.0",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.11",
+ "string.prototype.repeat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
+ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.5",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
+ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/eslint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/eslint/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
"dependencies": {
- "get-intrinsic": "^1.2.4",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.1"
+ "is-glob": "^4.0.3"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=10.13.0"
}
},
- "node_modules/es-to-primitive": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
- "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "node_modules/eslint/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
"dependencies": {
- "is-callable": "^1.1.4",
- "is-date-object": "^1.0.1",
- "is-symbol": "^1.0.2"
+ "type-fest": "^0.20.2"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=8"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/escalade": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
- "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "node_modules/eslint/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
"engines": {
- "node": ">=6"
+ "node": ">=8"
}
},
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ "node_modules/eslint/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "node_modules/eslint/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/eslint/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eslint/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
"engines": {
"node": ">=10"
},
@@ -8212,35 +9588,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/escodegen": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
- "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"dependencies": {
- "esprima": "^4.0.1",
- "estraverse": "^5.2.0",
- "esutils": "^2.0.2"
- },
- "bin": {
- "escodegen": "bin/escodegen.js",
- "esgenerate": "bin/esgenerate.js"
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
},
"engines": {
- "node": ">=6.0"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
- "optionalDependencies": {
- "source-map": "~0.6.1"
- }
- },
- "node_modules/escodegen/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "optional": true,
- "engines": {
- "node": ">=0.10.0"
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/esprima": {
@@ -8255,6 +9617,30 @@
"node": ">=4"
}
},
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
@@ -8794,6 +10180,12 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
"node_modules/fast-loops": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz",
@@ -8873,6 +10265,18 @@
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz",
"integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA=="
},
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -9045,6 +10449,26 @@
"micromatch": "^4.0.2"
}
},
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "dev": true
+ },
"node_modules/flow-enums-runtime": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz",
@@ -9279,6 +10703,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-tsconfig": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz",
+ "integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==",
+ "dev": true,
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
"node_modules/getenv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz",
@@ -9376,6 +10812,12 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
"node_modules/graphql": {
"version": "15.8.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz",
@@ -9847,6 +11289,21 @@
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
+ "node_modules/is-async-function": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz",
+ "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@@ -9878,6 +11335,27 @@
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
+ "node_modules/is-bun-module": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.1.0.tgz",
+ "integrity": "sha512-4mTAVPlrXpaN3jtF0lsnPCMGnq4+qZjVIKq0HCpfcqf8OC1SM5oATCIAPM5V5FN05qp2NNnFndphmdZS9CV3hA==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.6.3"
+ }
+ },
+ "node_modules/is-bun-module/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -9961,6 +11439,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-finalizationregistry": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz",
+ "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
@@ -9978,6 +11468,21 @@
"node": ">=6"
}
},
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -10027,6 +11532,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-negative-zero": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
@@ -10116,6 +11633,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-shared-array-buffer": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
@@ -10202,6 +11731,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -10213,6 +11754,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-weakset": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+ "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -10350,6 +11907,19 @@
"node": ">=8"
}
},
+ "node_modules/iterator.prototype": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz",
+ "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "get-intrinsic": "^1.2.1",
+ "has-symbols": "^1.0.3",
+ "reflect.getprototypeof": "^1.0.4",
+ "set-function-name": "^2.0.1"
+ }
+ },
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
@@ -12515,6 +14085,12 @@
"node": ">=4"
}
},
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
"node_modules/json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@@ -12559,6 +14135,12 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -12578,6 +14160,36 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/just-extend": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz",
+ "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==",
+ "dev": true
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -12602,6 +14214,19 @@
"node": ">=6"
}
},
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/lighthouse-logger": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz",
@@ -12838,11 +14463,23 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+ "dev": true
+ },
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
},
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
@@ -13662,6 +15299,15 @@
"node": ">=4"
}
},
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -13868,6 +15514,28 @@
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
+ "node_modules/nise": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz",
+ "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0",
+ "@sinonjs/fake-timers": "^11.2.2",
+ "@sinonjs/text-encoding": "^0.7.2",
+ "just-extend": "^6.2.0",
+ "path-to-regexp": "^6.2.1"
+ }
+ },
+ "node_modules/nise/node_modules/@sinonjs/fake-timers": {
+ "version": "11.3.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz",
+ "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1"
+ }
+ },
"node_modules/nocache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz",
@@ -14083,6 +15751,69 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object.entries": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz",
+ "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+ "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz",
+ "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -14145,6 +15876,23 @@
"opencollective-postinstall": "index.js"
}
},
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/ora": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz",
@@ -14269,6 +16017,18 @@
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="
},
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -14376,6 +16136,12 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
},
+ "node_modules/path-to-regexp": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz",
+ "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==",
+ "dev": true
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -14557,6 +16323,15 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -15338,6 +17113,40 @@
"node": ">=0.10.0"
}
},
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
+ "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.1",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "globalthis": "^1.0.3",
+ "which-builtin-type": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -15500,6 +17309,15 @@
"node": ">=8"
}
},
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
"node_modules/resolve.exports": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
@@ -15913,6 +17731,54 @@
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
+ "node_modules/sinon": {
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz",
+ "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1",
+ "@sinonjs/fake-timers": "^11.2.2",
+ "@sinonjs/samsam": "^8.0.0",
+ "diff": "^5.2.0",
+ "nise": "^6.0.0",
+ "supports-color": "^7"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/sinon"
+ }
+ },
+ "node_modules/sinon/node_modules/@sinonjs/fake-timers": {
+ "version": "11.3.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz",
+ "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1"
+ }
+ },
+ "node_modules/sinon/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sinon/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -16210,6 +18076,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ }
+ },
"node_modules/string.prototype.trim": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz",
@@ -16304,6 +18180,18 @@
"node": ">=6"
}
},
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -16444,6 +18332,15 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
@@ -16771,16 +18668,73 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/ts-api-utils": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+ "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
},
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
},
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@@ -17061,6 +19015,15 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
"node_modules/url-join": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz",
@@ -17284,6 +19247,56 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/which-builtin-type": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz",
+ "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==",
+ "dev": true,
+ "dependencies": {
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.0.5",
+ "is-finalizationregistry": "^1.0.2",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.1.4",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.15"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
@@ -17312,6 +19325,15 @@
"resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz",
"integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg=="
},
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/app/package.json b/app/package.json
index a85f5367..c549271f 100644
--- a/app/package.json
+++ b/app/package.json
@@ -8,14 +8,12 @@
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
- "test": "jest --watchAll",
+ "test": "jest --coverage",
+ "test:fails": "jest --watch --bail",
"lint": "expo lint",
"build:staging": "node build --type=staging",
"build:prod": "node build --type=production"
},
- "jest": {
- "preset": "jest-expo"
- },
"dependencies": {
"@expo/vector-icons": "^14.0.2",
"@meteorrn/core": "^2.8.2-rc.1",
@@ -59,12 +57,20 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
+ "@testing-library/react-native": "^12.6.1",
"@types/jest": "^29.5.12",
"@types/react": "~18.2.45",
"@types/react-test-renderer": "^18.0.7",
+ "eslint": "^8.57.0",
+ "eslint-config-expo": "^7.1.2",
+ "eslint-config-standard": "^17.1.0",
+ "eslint-plugin-jest": "^28.8.2",
+ "eslint-plugin-n": "^17.10.2",
+ "eslint-plugin-promise": "^7.1.0",
"jest": "^29.2.1",
"jest-expo": "~51.0.3",
"react-test-renderer": "18.2.0",
+ "sinon": "^18.0.0",
"typescript": "~5.3.3"
},
"private": true
diff --git a/app/settings/settings.json b/app/settings/settings.json
index a763f7da..6df651b7 100644
--- a/app/settings/settings.json
+++ b/app/settings/settings.json
@@ -1,12 +1,12 @@
{
- "isDevelopment": false,
- "isDeveloperRelease": false,
+ "isDevelopment": true,
+ "isDeveloperRelease": true,
"appToken": "01234567890123456789012345678901",
"backend": {
- "url": "wss://appbackend.lealernen.de/websocket",
- "reachabilityUrl": "https://appbackend.lealernen.de/reachability",
- "maxTimeout": 100000,
- "interval": 500,
+ "url": "ws://192.168.178.46:8080/websocket",
+ "reachabilityUrl": "http://192.168.178.46:8080/reachability",
+ "maxTimeout": 3000,
+ "interval": 250,
"methods": {
"defaultTimeout": 5000,
"sendError": "clientErrors.methods.send",
@@ -46,7 +46,7 @@
}
},
"content": {
- "url": "https://content.lealernen.de"
+ "url": "http://192.168.178.22:3030"
},
"log": {
"level": 0,
@@ -71,4 +71,4 @@
"AppSession": false,
"connection": false
}
-}
+}
\ No newline at end of file
From 4c407da975bd3514c0d2279b24d39342bb3e1c93 Mon Sep 17 00:00:00 2001
From: jankapunkt
Date: Tue, 3 Sep 2024 11:48:06 +0200
Subject: [PATCH 2/4] docs: updated backend/app docs
---
app/jsdoc.conf.json | 26 +
app/lib/hooks/useRefresh.js | 2 +-
app/package-lock.json | 205 +++++
app/package.json | 2 +
docs/api/app/App.js.html | 32 +-
docs/api/app/AuthenticationError.html | 4 +-
docs/api/app/ChoiceImageInstructions.html | 228 +++++
docs/api/app/ClozeRenderer.html | 4 +-
docs/api/app/ConnectItemRenderer.html | 6 +-
docs/api/app/ConnectionError.html | 4 +-
docs/api/app/ImageRenderer.html | 6 +-
docs/api/app/LeaCollection.html | 4 +-
docs/api/app/Loading.html | 286 ++++++
docs/api/app/MeteorError_MeteorError.html | 4 +-
docs/api/app/SyncScreen.html | 4 +-
docs/api/app/UnitContentElementFactory.html | 4 +-
docs/api/app/components_ActionButton.js.html | 4 +-
.../api/app/components_CharacterInput.js.html | 4 +-
docs/api/app/components_Checkbox.js.html | 4 +-
docs/api/app/components_Confirm.js.html | 8 +-
docs/api/app/components_Connecting.js.html | 69 +-
docs/api/app/components_ErrorMessage.js.html | 38 +-
docs/api/app/components_FadePanel.js.html | 4 +-
docs/api/app/components_LeaButton.js.html | 10 +-
.../api/app/components_LeaButtonGroup.js.html | 4 +-
docs/api/app/components_LeaText.js.html | 4 +-
docs/api/app/components_Loading.js.html | 107 +++
.../app/components_MarkdownWithTTS.js.html | 4 +-
docs/api/app/components_NullComponent.js.html | 4 +-
docs/api/app/components_RouteButton.js.html | 4 +-
docs/api/app/components_SoundIcon.js.html | 4 +-
docs/api/app/components_Tts.js.html | 43 +-
...actories_UnitContentElementFactory.js.html | 4 +-
..._factories_createRoutableComponent.js.html | 4 +-
.../api/app/components_images_LeaLogo.js.html | 6 +-
...omponents_progress_CurrentProgress.js.html | 4 +-
.../app/components_progress_Diamond.js.html | 4 +-
...ts_progress_StaticCircularProgress.js.html | 4 +-
...ts_progress_correctDiamondProgress.js.html | 4 +-
...nents_renderer_media_ImageRenderer.js.html | 35 +-
.../components_renderer_text_Markdown.js.html | 4 +-
...ts_renderer_text_PlainTextRenderer.js.html | 4 +-
docs/api/app/constants_Colors.js.html | 4 +-
docs/api/app/constants_Layout.js.html | 37 +-
docs/api/app/contexts_UserProgress.js.html | 4 +-
.../contexts_collectionNotInitialized.js.html | 4 +-
docs/api/app/dev_DeveloperScreen.js.html | 4 +-
docs/api/app/dev_loadDevData.js.html | 4 +-
docs/api/app/dev_resetSyncData.js.html | 4 +-
docs/api/app/env_Config.js.html | 7 +-
.../env_loadSettingsFromUserProfile.js.html | 4 +-
.../app/errors_AuthenticationError.js.html | 4 +-
docs/api/app/errors_ConnectionError.js.html | 4 +-
docs/api/app/errors_MeteorError.js.html | 4 +-
docs/api/app/global.html | 814 +++++++++++++++++-
docs/api/app/hooks_useBackHandler.js.html | 4 +-
docs/api/app/hooks_useConnection.js.html | 156 ++--
docs/api/app/hooks_useLogin.js.html | 65 +-
docs/api/app/hooks_useRefresh.js.html | 66 ++
docs/api/app/index.html | 12 +-
docs/api/app/infrastructure_Log.js.html | 4 +-
...tructure_collections_LeaCollection.js.html | 4 +-
...astructure_collections_collections.js.html | 4 +-
.../infrastructure_createCollection.js.html | 4 +-
.../infrastructure_createRepository.js.html | 4 +-
...nfrastructure_log_InteractionGraph.js.html | 4 +-
docs/api/app/infrastructure_sync_Sync.js.html | 4 +-
.../app/items_choice_ChoiceRenderer.js.html | 4 +-
...ms_choice_getChoiceEntryScoreColor.js.html | 4 +-
docs/api/app/items_cloze_Cloze.js.html | 4 +-
.../api/app/items_cloze_ClozeRenderer.js.html | 4 +-
.../items_cloze_ClozeRendererBlank.js.html | 4 +-
.../items_cloze_ClozeRendererSelect.js.html | 4 +-
.../app/items_cloze_ClozeTokenizer.js.html | 4 +-
...cloze_createScoringSummaryForInput.js.html | 6 +-
.../items_connect_ConnectItemRenderer.js.html | 194 +++--
...getCompareValuesForSelectableItems.js.html | 4 +-
docs/api/app/items_utils_CompareState.js.html | 4 +-
.../api/app/items_utils_KeyboardTypes.js.html | 4 +-
docs/api/app/meteor_call.js.html | 35 +-
docs/api/app/meteor_ensureConnected.js.html | 4 +-
docs/api/app/meteor_updateUserProfile.js.html | 4 +-
docs/api/app/meteor_useDocs.js.html | 100 ++-
.../api/app/navigation_MainNavigation.js.html | 25 +-
docs/api/app/remotes_ContentServer.js.html | 4 +-
docs/api/app/schema_createSchema.js.html | 4 +-
.../app/schema_validateSettingsSchema.js.html | 6 +-
docs/api/app/scoring_Scoring.js.html | 4 +-
docs/api/app/scoring_ScoringTypes.js.html | 4 +-
docs/api/app/scoring_getScoring.js.html | 4 +-
.../app/scoring_isUndefinedResponse.js.html | 4 +-
docs/api/app/screens_BaseScreen.js.html | 51 +-
.../screens_auth_RegistrationScreen.js.html | 18 +-
...eens_auth_TermsAndConditionsScreen.js.html | 10 +-
.../app/screens_auth_WelcomeScreen.js.html | 4 +-
.../screens_complete_CompleteScreen.js.html | 9 +-
docs/api/app/screens_home_HomeScreen.js.html | 24 +-
.../api/app/screens_home_loadHomeData.js.html | 75 ++
.../app/screens_map_DimensionScreen.js.html | 4 +-
docs/api/app/screens_map_MapScreen.js.html | 143 +--
.../screens_map_components_Connector.js.html | 103 ++-
.../screens_map_components_Milestone.js.html | 25 +-
.../app/screens_map_components_Stage.js.html | 4 +-
docs/api/app/screens_map_loadMapData.js.html | 18 +-
.../app/screens_profile_ProfileScreen.js.html | 25 +-
...creens_profile_account_AccountInfo.js.html | 52 +-
...le_achievements_AchievementsScreen.js.html | 4 +-
..._achievements_loadAchievementsData.js.html | 4 +-
docs/api/app/screens_sync_SyncScreen.js.html | 4 +-
docs/api/app/screens_unit_UnitScreen.js.html | 12 +-
.../api/app/screens_unit_completeUnit.js.html | 4 +-
.../screens_unit_createResponseDoc.js.html | 4 +-
.../screens_unit_getDimensionColor.js.html | 4 +-
...structions_ChoiceImageInstructions.js.html | 206 +++++
...eens_unit_renderer_ContentRenderer.js.html | 4 +-
...derer_InstructionsGraphicsRenderer.js.html | 25 +-
...screens_unit_renderer_UnitRenderer.js.html | 102 ++-
...eens_unit_renderer_UnitSetRenderer.js.html | 4 +-
.../screens_unit_shouldRenderStory.js.html | 4 +-
docs/api/app/state_AppSession.js.html | 4 +-
docs/api/app/state_createStorageAPI.js.html | 4 +-
docs/api/app/styles_createStyleSheet.js.html | 4 +-
docs/api/app/tts_TTSVoiceConfig.js.html | 78 +-
docs/api/app/utils_array_byDocId.js.html | 4 +-
docs/api/app/utils_array_byOrderedIds.js.html | 4 +-
.../utils_array_randomArrayElement.js.html | 4 +-
docs/api/app/utils_array_toArrayIfNot.js.html | 4 +-
docs/api/app/utils_array_toDocId.js.html | 4 +-
docs/api/app/utils_asyncTimeout.js.html | 4 +-
docs/api/app/utils_createTimedPromise.js.html | 6 +-
.../app/utils_math_randomIntInclusive.js.html | 4 +-
.../app/utils_number_isSafeInteger.js.html | 4 +-
.../app/utils_number_isValidNumber.js.html | 4 +-
docs/api/app/utils_number_toInteger.js.html | 4 +-
.../utils_number_toPrecisionNumber.js.html | 4 +-
docs/api/app/utils_object_clearObject.js.html | 4 +-
docs/api/app/utils_object_hasOwnProp.js.html | 4 +-
docs/api/app/utils_object_isDefined.js.html | 4 +-
docs/api/app/utils_simpleRandomHex.js.html | 4 +-
.../utils_text_createSimpleTokenizer.js.html | 4 +-
docs/api/app/utils_text_isWord.js.html | 4 +-
...s_trigonometry_getPositionOnCircle.js.html | 4 +-
142 files changed, 3183 insertions(+), 807 deletions(-)
create mode 100644 app/jsdoc.conf.json
create mode 100644 docs/api/app/ChoiceImageInstructions.html
create mode 100644 docs/api/app/Loading.html
create mode 100644 docs/api/app/components_Loading.js.html
create mode 100644 docs/api/app/hooks_useRefresh.js.html
create mode 100644 docs/api/app/screens_home_loadHomeData.js.html
create mode 100644 docs/api/app/screens_unit_instructions_ChoiceImageInstructions.js.html
diff --git a/app/jsdoc.conf.json b/app/jsdoc.conf.json
new file mode 100644
index 00000000..431f0df4
--- /dev/null
+++ b/app/jsdoc.conf.json
@@ -0,0 +1,26 @@
+{
+ "tags": {
+ "allowUnknownTags": true,
+ "dictionaries": ["jsdoc", "closure"]
+ },
+ "source": {
+ "include": ["."],
+ "exclude": [
+ ".expo",
+ ".expo-shared",
+ "__mocks__",
+ "__tests__",
+ "node_modules"
+ ],
+ "includePattern": ".+\\.js(doc|x)?$",
+ "excludePattern": "(^|\\/|\\\\)_"
+ },
+ "plugins": [
+ "plugins/markdown"
+ ],
+ "opts": {
+ "destination": "../docs/api/app",
+ "recurse": true,
+ "readme": "../README.md"
+ }
+}
diff --git a/app/lib/hooks/useRefresh.js b/app/lib/hooks/useRefresh.js
index 13061b6b..5e4149a1 100644
--- a/app/lib/hooks/useRefresh.js
+++ b/app/lib/hooks/useRefresh.js
@@ -4,7 +4,7 @@ import { useCallback, useState } from 'react'
* A little helper hook, that can be used to mediate between
* {useDocs} and {BaseScreen} in order to implement a
* page-refresh functionality.
- * @return {[number,(function(): void)|*]}
+ * @return {Array}
*/
export const useRefresh = () => {
const [reload, setReload] = useState(0)
diff --git a/app/package-lock.json b/app/package-lock.json
index a4c1406a..c8537607 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -62,6 +62,7 @@
"eslint-plugin-promise": "^7.1.0",
"jest": "^29.2.1",
"jest-expo": "~51.0.3",
+ "jsdoc": "^4.0.3",
"react-test-renderer": "18.2.0",
"sinon": "^18.0.0",
"typescript": "~5.3.3"
@@ -3722,6 +3723,18 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@jsdoc/salty": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz",
+ "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.21"
+ },
+ "engines": {
+ "node": ">=v12.0.0"
+ }
+ },
"node_modules/@meteorrn/core": {
"version": "2.8.2-rc.1",
"resolved": "https://registry.npmjs.org/@meteorrn/core/-/core-2.8.2-rc.1.tgz",
@@ -6187,6 +6200,28 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
+ "node_modules/@types/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
+ "dev": true
+ },
+ "node_modules/@types/markdown-it": {
+ "version": "14.1.2",
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+ "dev": true,
+ "dependencies": {
+ "@types/linkify-it": "^5",
+ "@types/mdurl": "^2"
+ }
+ },
+ "node_modules/@types/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
+ "dev": true
+ },
"node_modules/@types/node": {
"version": "18.19.47",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.47.tgz",
@@ -7291,6 +7326,12 @@
"node": ">= 6"
}
},
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true
+ },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -7594,6 +7635,18 @@
}
]
},
+ "node_modules/catharsis": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
+ "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.15"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -13909,6 +13962,15 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/js2xmlparser": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz",
+ "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==",
+ "dev": true,
+ "dependencies": {
+ "xmlcreate": "^2.0.4"
+ }
+ },
"node_modules/jsc-android": {
"version": "250231.0.0",
"resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz",
@@ -14015,6 +14077,100 @@
"node": ">=8"
}
},
+ "node_modules/jsdoc": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.3.tgz",
+ "integrity": "sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.15",
+ "@jsdoc/salty": "^0.2.1",
+ "@types/markdown-it": "^14.1.1",
+ "bluebird": "^3.7.2",
+ "catharsis": "^0.9.0",
+ "escape-string-regexp": "^2.0.0",
+ "js2xmlparser": "^4.0.2",
+ "klaw": "^3.0.0",
+ "markdown-it": "^14.1.0",
+ "markdown-it-anchor": "^8.6.7",
+ "marked": "^4.0.10",
+ "mkdirp": "^1.0.4",
+ "requizzle": "^0.2.3",
+ "strip-json-comments": "^3.1.0",
+ "underscore": "~1.13.2"
+ },
+ "bin": {
+ "jsdoc": "jsdoc.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/jsdoc/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/jsdoc/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jsdoc/node_modules/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+ "dev": true,
+ "dependencies": {
+ "uc.micro": "^2.0.0"
+ }
+ },
+ "node_modules/jsdoc/node_modules/markdown-it": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.4.0",
+ "linkify-it": "^5.0.0",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.mjs"
+ }
+ },
+ "node_modules/jsdoc/node_modules/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+ "dev": true
+ },
+ "node_modules/jsdoc/node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jsdoc/node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "dev": true
+ },
"node_modules/jsdom": {
"version": "20.0.3",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
@@ -14198,6 +14354,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/klaw": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
+ "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.9"
+ }
+ },
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -14717,6 +14882,16 @@
"markdown-it": "bin/markdown-it.js"
}
},
+ "node_modules/markdown-it-anchor": {
+ "version": "8.6.7",
+ "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz",
+ "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==",
+ "dev": true,
+ "peerDependencies": {
+ "@types/markdown-it": "*",
+ "markdown-it": "*"
+ }
+ },
"node_modules/markdown-it/node_modules/entities": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
@@ -16438,6 +16613,15 @@
"node": ">=6"
}
},
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/pure-rand": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
@@ -17273,6 +17457,15 @@
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"dev": true
},
+ "node_modules/requizzle": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz",
+ "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.21"
+ }
+ },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -18896,6 +19089,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/underscore": {
+ "version": "1.13.7",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
+ "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
+ "dev": true
+ },
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
@@ -19525,6 +19724,12 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
+ "node_modules/xmlcreate": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz",
+ "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==",
+ "dev": true
+ },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/app/package.json b/app/package.json
index c549271f..db4dda9d 100644
--- a/app/package.json
+++ b/app/package.json
@@ -11,6 +11,7 @@
"test": "jest --coverage",
"test:fails": "jest --watch --bail",
"lint": "expo lint",
+ "build:docs": "jsdoc -c jsdoc.conf.json",
"build:staging": "node build --type=staging",
"build:prod": "node build --type=production"
},
@@ -69,6 +70,7 @@
"eslint-plugin-promise": "^7.1.0",
"jest": "^29.2.1",
"jest-expo": "~51.0.3",
+ "jsdoc": "^4.0.3",
"react-test-renderer": "18.2.0",
"sinon": "^18.0.0",
"typescript": "~5.3.3"
diff --git a/docs/api/app/App.js.html b/docs/api/app/App.js.html
index d81829d2..f26eec8e 100644
--- a/docs/api/app/App.js.html
+++ b/docs/api/app/App.js.html
@@ -26,7 +26,7 @@ Source: App.js
- import React, { useCallback } from 'react'
+ import React, { useCallback, useEffect, useState } from 'react'
import './i18n'
import { useSplashScreen } from './hooks/useSplashScreen'
import { useConnection } from './hooks/useConnection'
@@ -40,10 +40,8 @@ Source: App.js
import { CatchErrors } from './components/CatchErrors'
import { initSound } from './startup/initSound'
import { validateSettingsSchema } from './schema/validateSettingsSchema'
-import { initFonts } from './startup/initFonts'
const initFunctions = [
- initFonts,
initExceptionHandling,
validateSettingsSchema,
initContexts,
@@ -59,13 +57,23 @@ Source: App.js
*/
export const App = function App () {
const { appIsReady, error, onLayoutRootView } = useSplashScreen(initFunctions)
- const { connected } = useConnection()
- const renderConnectionStatus = useCallback(() => {
- if (connected) {
- return null
+ const connection = useConnection()
+ const [showWarning, setShowWarning] = useState(false)
+
+ useEffect(() => {
+ if (showWarning && connection.connected) {
+ setShowWarning(false)
+ }
+ if (!showWarning && !connection.connected) {
+ setShowWarning(true)
}
- return (<Connecting />)
- }, [connected])
+ }, [showWarning, connection.connected])
+
+ const renderConnectionStatus = useCallback(() => {
+ return showWarning
+ ? (<Connecting connection={connection} />)
+ : null
+ }, [showWarning, connection.www, connection.backend])
// splashscreen is still active...
if (!appIsReady) { return null }
@@ -82,7 +90,7 @@ Source: App.js
return (
<CatchErrors>
- <MainNavigation onLayout={onLayoutRootView} />
+ <MainNavigation onLayout={onLayoutRootView} connection={connection} />
{renderConnectionStatus()}
</CatchErrors>
)
@@ -97,13 +105,13 @@ Source: App.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/AuthenticationError.html b/docs/api/app/AuthenticationError.html
index ab1943d4..a8746d0c 100644
--- a/docs/api/app/AuthenticationError.html
+++ b/docs/api/app/AuthenticationError.html
@@ -272,13 +272,13 @@ Classes
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/ChoiceImageInstructions.html b/docs/api/app/ChoiceImageInstructions.html
new file mode 100644
index 00000000..9d74ff86
--- /dev/null
+++ b/docs/api/app/ChoiceImageInstructions.html
@@ -0,0 +1,228 @@
+
+
+
+
+ JSDoc: Class: ChoiceImageInstructions
+
+
+
+
+
+
+
+
+
+
+
+
+
Class: ChoiceImageInstructions
+
+
+
+
+
+
+
+
+
+
+ ChoiceImageInstructions(props) → {Element}
+
+
+
+
+
+
+
+
+
+
+
+
+
new ChoiceImageInstructions(props) → {Element}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes Global
+
+
+
+
+
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/api/app/ClozeRenderer.html b/docs/api/app/ClozeRenderer.html
index 0e79570b..a253cdae 100644
--- a/docs/api/app/ClozeRenderer.html
+++ b/docs/api/app/ClozeRenderer.html
@@ -336,13 +336,13 @@ Returns:
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/ConnectItemRenderer.html b/docs/api/app/ConnectItemRenderer.html
index a69ec43a..c72c68af 100644
--- a/docs/api/app/ConnectItemRenderer.html
+++ b/docs/api/app/ConnectItemRenderer.html
@@ -147,7 +147,7 @@ Parameters:
Source:
@@ -227,13 +227,13 @@ Returns:
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/ConnectionError.html b/docs/api/app/ConnectionError.html
index 1e83405a..a979988b 100644
--- a/docs/api/app/ConnectionError.html
+++ b/docs/api/app/ConnectionError.html
@@ -278,13 +278,13 @@ Classes
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/ImageRenderer.html b/docs/api/app/ImageRenderer.html
index 68fd093c..cc79e1de 100644
--- a/docs/api/app/ImageRenderer.html
+++ b/docs/api/app/ImageRenderer.html
@@ -268,7 +268,7 @@ Properties
Source:
@@ -351,13 +351,13 @@ Returns:
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/LeaCollection.html b/docs/api/app/LeaCollection.html
index 4ef1c114..697d25e3 100644
--- a/docs/api/app/LeaCollection.html
+++ b/docs/api/app/LeaCollection.html
@@ -318,13 +318,13 @@ Returns:
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/Loading.html b/docs/api/app/Loading.html
new file mode 100644
index 00000000..2aa7d526
--- /dev/null
+++ b/docs/api/app/Loading.html
@@ -0,0 +1,286 @@
+
+
+
+
+ JSDoc: Class: Loading
+
+
+
+
+
+
+
+
+
+
+
+
+
Class: Loading
+
+
+
+
+
+
+
+
+
+
+ Loading(text, color, style, timeOut) → {Element}
+
+
+
+
+
+
+
+
+
+
+
+
+
new Loading(text, color, style, timeOut) → {Element}
+
+
+
+
+
+
+
+
Renders a ActivityIndicator with an optional message.
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ text
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ color
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ style
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ timeOut
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classes Global
+
+
+
+
+
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/api/app/MeteorError_MeteorError.html b/docs/api/app/MeteorError_MeteorError.html
index d08be771..c0e35665 100644
--- a/docs/api/app/MeteorError_MeteorError.html
+++ b/docs/api/app/MeteorError_MeteorError.html
@@ -249,13 +249,13 @@ Parameters:
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/SyncScreen.html b/docs/api/app/SyncScreen.html
index a38c4201..be6d3a74 100644
--- a/docs/api/app/SyncScreen.html
+++ b/docs/api/app/SyncScreen.html
@@ -246,13 +246,13 @@ Returns:
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/UnitContentElementFactory.html b/docs/api/app/UnitContentElementFactory.html
index 03c2a5b2..070c7e98 100644
--- a/docs/api/app/UnitContentElementFactory.html
+++ b/docs/api/app/UnitContentElementFactory.html
@@ -853,13 +853,13 @@ Parameters:
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_ActionButton.js.html b/docs/api/app/components_ActionButton.js.html
index 29f7a8af..092ca051 100644
--- a/docs/api/app/components_ActionButton.js.html
+++ b/docs/api/app/components_ActionButton.js.html
@@ -108,13 +108,13 @@ Source: components/ActionButton.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_CharacterInput.js.html b/docs/api/app/components_CharacterInput.js.html
index b971a46b..bc9a05fc 100644
--- a/docs/api/app/components_CharacterInput.js.html
+++ b/docs/api/app/components_CharacterInput.js.html
@@ -229,13 +229,13 @@ Source: components/CharacterInput.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_Checkbox.js.html b/docs/api/app/components_Checkbox.js.html
index e2765bbf..4bed58fd 100644
--- a/docs/api/app/components_Checkbox.js.html
+++ b/docs/api/app/components_Checkbox.js.html
@@ -147,13 +147,13 @@ Source: components/Checkbox.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_Confirm.js.html b/docs/api/app/components_Confirm.js.html
index 7780fe75..fb213ba5 100644
--- a/docs/api/app/components_Confirm.js.html
+++ b/docs/api/app/components_Confirm.js.html
@@ -32,7 +32,7 @@ Source: components/Confirm.js
import { createStyleSheet } from '../styles/createStyleSheet'
import { useTts } from './Tts'
import { Colors } from '../constants/Colors'
-import { Icon } from 'react-native-elements'
+import Icon from '@expo/vector-icons/FontAwesome6'
import { makeTransparent } from '../styles/makeTransparent'
import { Layout } from '../constants/Layout'
@@ -89,7 +89,7 @@ Source: components/Confirm.js
return (
<Pressable accessibilityRole='button' style={styles.buttonContainer} onPress={onPress}>
<Icon
- name={props.icon} type='font-awesome-5' color={Colors.secondary}
+ name={props.icon} color={Colors.secondary}
style
size={18}
/>
@@ -208,13 +208,13 @@ Source: components/Confirm.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_Connecting.js.html b/docs/api/app/components_Connecting.js.html
index e65db247..d6d2dfbf 100644
--- a/docs/api/app/components_Connecting.js.html
+++ b/docs/api/app/components_Connecting.js.html
@@ -26,35 +26,67 @@ Source: components/Connecting.js
- import React from 'react'
-import { ActivityIndicator, Modal, View } from 'react-native'
+ import React, { useState } from 'react'
+import { Modal, View } from 'react-native'
import { createStyleSheet } from '../styles/createStyleSheet'
import { Layout } from '../constants/Layout'
import { useTranslation } from 'react-i18next'
import { useTts } from './Tts'
import { makeTransparent } from '../styles/makeTransparent'
import { Colors } from '../constants/Colors'
+import Icon from '@expo/vector-icons/FontAwesome'
+import { useDevelopment } from '../hooks/useDevelopment'
+
+const iconSize = 46
+const minusSize = 20
/**
* Default visual for indicating to users, that we are connecting to the servers.
* @returns {JSX.Element}
* @component
*/
-export const Connecting = () => {
+export const Connecting = ({ connection }) => {
const { t } = useTranslation()
- const { Tts } = useTts()
+ const [visible, setVisible] = useState(true)
+ const dev = useDevelopment()
+ const reachBackend = connection.www && connection.backend
+ let description
+
+ if (reachBackend) {
+ description = t('connecting.done')
+ }
+ else if (connection.www) {
+ description = t('connecting.backend')
+ }
+ else {
+ description = t('connecting.www')
+ }
+
+ const close = () => {
+ if (!dev.isDeveloperRelease && !dev.isDevelopment) {
+ return
+ }
+ setVisible(!visible)
+ }
return (
<Modal
animationType='slide'
transparent
- visible
+ visible={visible}
+ onRequestClose={close}
>
<View style={styles.background}>
<View style={styles.content}>
<View style={styles.container}>
- <ActivityIndicator style={styles.indicator} color={Colors.primary} size='large' />
- <Tts text={t('connecting.title')} />
+ <View style={styles.row}>
+ <Icon name='mobile' size={iconSize} color={Colors.success} />
+ <Connection connected={connection.www} available />
+ <Icon name='wifi' size={iconSize} color={connection.www ? Colors.success : Colors.danger} />
+ <Connection connected={reachBackend} available={connection.www} />
+ <Icon name='cloud' size={iconSize} color={reachBackend ? Colors.success : Colors.danger} />
+ </View>
+ <Description text={description} />
</View>
</View>
</View>
@@ -62,6 +94,21 @@ Source: components/Connecting.js
)
}
+const Description = ({ text }) => {
+ const { Tts } = useTts()
+ return (<Tts text={text} />)
+}
+
+const Connection = ({ connected }) => {
+ const arrowColor = connected ? Colors.success : Colors.danger
+
+ return (
+ <View style={styles.row}>
+ <Icon name='minus' size={minusSize} color={arrowColor} />
+ </View>
+ )
+}
+
const styles = createStyleSheet({
background: {
flexGrow: 1,
@@ -80,6 +127,10 @@ Source: components/Connecting.js
alignItems: 'center',
...Layout.dropShadow({ elevation: 6 })
},
+ row: {
+ ...Layout.row(),
+ padding: 2
+ },
indicator: {
height: 50
}
@@ -94,13 +145,13 @@ Source: components/Connecting.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_ErrorMessage.js.html b/docs/api/app/components_ErrorMessage.js.html
index 71cafd1e..07faf821 100644
--- a/docs/api/app/components_ErrorMessage.js.html
+++ b/docs/api/app/components_ErrorMessage.js.html
@@ -26,10 +26,10 @@ Source: components/ErrorMessage.js
- import React from 'react'
+ import React, { useEffect } from 'react'
import { useTts } from './Tts'
import { ActionButton } from './ActionButton'
-import { Image, Text, View } from 'react-native'
+import { Image, Text, Vibration, View } from 'react-native'
import { createStyleSheet } from '../styles/createStyleSheet'
import { Colors } from '../constants/Colors'
import { Layout } from '../constants/Layout'
@@ -53,6 +53,10 @@ Source: components/ErrorMessage.js
const { t } = useTranslation()
const { Tts } = useTts()
+ useEffect(() => {
+ Vibration.vibrate(100)
+ }, [])
+
if (!error && !message) {
return null
}
@@ -71,8 +75,6 @@ Source: components/ErrorMessage.js
}
const debugError = () => {
- // if (!Config.isDevelopment) { return null }
-
return (
<View style={styles.container} accessibilityRole='alert'>
<Text>Debugging Info</Text>
@@ -89,6 +91,13 @@ Source: components/ErrorMessage.js
return (
<View style={styles.container} accessibilityRole='alert'>
+ <Tts
+ text={textBase}
+ fontStyle={styles.headline}
+ block
+ iconColor={Colors.danger}
+ color={Colors.secondary}
+ />
<Image
source={image.src}
style={styles.image}
@@ -96,12 +105,6 @@ Source: components/ErrorMessage.js
resizeMethod='resize'
resizeMode='contain'
/>
- <Tts
- text={textBase}
- block
- iconColor={Colors.danger}
- color={Colors.secondary}
- />
<Tts
text={t('errors.restart')}
block
@@ -115,7 +118,7 @@ Source: components/ErrorMessage.js
}
const image = {
- src: require('../assets/images/sorry.png')
+ src: require('../../assets/images/sorry.png')
}
/** @private */
@@ -130,8 +133,13 @@ Source: components/ErrorMessage.js
...Layout.dropShadow()
},
image: {
- flex: 1,
- width: '100%'
+ shrink: 1,
+ width: '100%',
+ marginTop: 10,
+ marginBottom: 10
+ },
+ headline: {
+ fontWeight: 'bold'
}
})
@@ -144,13 +152,13 @@ Source: components/ErrorMessage.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_FadePanel.js.html b/docs/api/app/components_FadePanel.js.html
index a110379c..0eea2f26 100644
--- a/docs/api/app/components_FadePanel.js.html
+++ b/docs/api/app/components_FadePanel.js.html
@@ -82,13 +82,13 @@ Source: components/FadePanel.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_LeaButton.js.html b/docs/api/app/components_LeaButton.js.html
index d899711b..79fb3471 100644
--- a/docs/api/app/components_LeaButton.js.html
+++ b/docs/api/app/components_LeaButton.js.html
@@ -26,7 +26,8 @@ Source: components/LeaButton.js
- import { Button, Icon } from 'react-native-elements'
+ import { Button } from 'react-native-elements'
+import Icon from '@expo/vector-icons/FontAwesome6'
import React, { useState } from 'react'
import { createStyleSheet } from '../styles/createStyleSheet'
import { Colors } from '../constants/Colors'
@@ -98,7 +99,7 @@ Source: components/LeaButton.js
setTimeout(async () => {
await props.onPress()
setPressed(false)
- }, 25)
+ }, 50)
}
}
@@ -139,7 +140,6 @@ Source: components/LeaButton.js
id: 'icon-id',
color: Colors.secondary,
size: 18,
- type: 'font-awesome-5',
position: 'left'
}
}
@@ -182,13 +182,13 @@ Source: components/LeaButton.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_LeaButtonGroup.js.html b/docs/api/app/components_LeaButtonGroup.js.html
index f8143716..f7964ad2 100644
--- a/docs/api/app/components_LeaButtonGroup.js.html
+++ b/docs/api/app/components_LeaButtonGroup.js.html
@@ -118,13 +118,13 @@ Source: components/LeaButtonGroup.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_LeaText.js.html b/docs/api/app/components_LeaText.js.html
index 38454db6..c510786b 100644
--- a/docs/api/app/components_LeaText.js.html
+++ b/docs/api/app/components_LeaText.js.html
@@ -95,13 +95,13 @@ Source: components/LeaText.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_Loading.js.html b/docs/api/app/components_Loading.js.html
new file mode 100644
index 00000000..33809c37
--- /dev/null
+++ b/docs/api/app/components_Loading.js.html
@@ -0,0 +1,107 @@
+
+
+
+
+ JSDoc: Source: components/Loading.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: components/Loading.js
+
+
+
+
+
+
+
+
+ import React, { useCallback, useEffect, useState } from 'react'
+import { View, ActivityIndicator } from 'react-native'
+import { useTts } from './Tts'
+import { Colors } from '../constants/Colors'
+import { createStyleSheet } from '../styles/createStyleSheet'
+import { mergeStyles } from '../styles/mergeStyles'
+
+/**
+ * Renders a ActivityIndicator with an optional message.
+ * @param text
+ * @param color
+ * @param style
+ * @param timeOut
+ * @return {Element}
+ * @constructor
+ */
+export const Loading = ({ text, color, style, timeOut = 700 }) => {
+ const [showText, setShowText] = useState(false)
+ const { Tts } = useTts()
+ const renderText = useCallback(() => {
+ if (!text || !showText) return null
+ return (
+ <Tts text={text} />
+ )
+ }, [text, showText])
+
+ useEffect(() => {
+ let timer
+ if (timeOut > 0) {
+ timer = setTimeout(() => setShowText(true), timeOut)
+ }
+ else {
+ setShowText(true)
+ }
+ // prevent memleak
+ return () => clearTimeout(timer)
+ }, [timeOut])
+
+ const containerStyle = style
+ ? mergeStyles(styles.container, style)
+ : styles.container
+
+ return (
+ <View style={containerStyle}>
+ <ActivityIndicator size='large' color={color ?? Colors.secondary} />
+ {renderText()}
+ </View>
+ )
+}
+
+const styles = createStyleSheet({
+ container: {
+ alignItems: 'center',
+ justifyContent: 'center'
+ }
+})
+
+
+
+
+
+
+
+
+
+
+ Classes Global
+
+
+
+
+
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
+
+
+
+
+
+
diff --git a/docs/api/app/components_MarkdownWithTTS.js.html b/docs/api/app/components_MarkdownWithTTS.js.html
index f14221f3..ceab8727 100644
--- a/docs/api/app/components_MarkdownWithTTS.js.html
+++ b/docs/api/app/components_MarkdownWithTTS.js.html
@@ -150,13 +150,13 @@ Source: components/MarkdownWithTTS.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_NullComponent.js.html b/docs/api/app/components_NullComponent.js.html
index e4ada92e..73c9a879 100644
--- a/docs/api/app/components_NullComponent.js.html
+++ b/docs/api/app/components_NullComponent.js.html
@@ -42,13 +42,13 @@ Source: components/NullComponent.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_RouteButton.js.html b/docs/api/app/components_RouteButton.js.html
index 1eccc478..3926e971 100644
--- a/docs/api/app/components_RouteButton.js.html
+++ b/docs/api/app/components_RouteButton.js.html
@@ -85,13 +85,13 @@ Source: components/RouteButton.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_SoundIcon.js.html b/docs/api/app/components_SoundIcon.js.html
index 31ad2340..39c9cb20 100644
--- a/docs/api/app/components_SoundIcon.js.html
+++ b/docs/api/app/components_SoundIcon.js.html
@@ -125,13 +125,13 @@ Source: components/SoundIcon.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_Tts.js.html b/docs/api/app/components_Tts.js.html
index 6d33c442..40ca6189 100644
--- a/docs/api/app/components_Tts.js.html
+++ b/docs/api/app/components_Tts.js.html
@@ -69,26 +69,28 @@ Source: components/Tts.js
* Tts stands for Text-To-Speech. It contains an icon and the text to be spoken.
*
* @category Components
- * @param {string} props.text: The displayed and spoken text
- * @param {string} props.ttsText: The spoken text, use this if it differs from written text
- * @param {boolean} props.dontShowText: Determines whether the text is displayed (Default 'true')
- * @param {boolean} props.smallButton: Changes the button size from 20 to 15 (Default 'false')
- * @param {boolean=} props.block: Makes the container flexGrow. If this causes problems, use style instead.
- * @param {boolean=} props.asButton: Makes the container a block-sized button
- * @param {boolean=} props.disabled: Makes the button disabled
- * @param {string=} props.color: The color of the icon and the text, in hexadecimal format. Default: Colors.secondary
+ * @param props {object}
+ * @param props.text {string} The displayed and spoken text
+ * @param props.ttsText {string} The spoken text, use this if it differs from written text
+ * @param props.dontShowText {boolean} Determines whether the text is displayed (Default 'true')
+ * @param props.smallButton {boolean} Changes the button size from 20 to 15 (Default 'false')
+ * @param props.block {boolean=} Makes the container flexGrow. If this causes problems, use style instead.
+ * @param props.asButton {boolean=} Makes the container a block-sized button
+ * @param props.disabled {boolean=} Makes the button disabled
+ * @param props.color {string=} The color of the icon and the text, in hexadecimal format. Default: Colors.secondary
* (examples in ./constants/Colors.js)
- * @param {string=} props.iconColor: The color of the icon in hexadecimal format. Default: Colors.secondary (examples
+ * @param props.iconColor {string=} The color of the icon in hexadecimal format. Default: Colors.secondary (examples
* in ./constants/Colors.js)
- * @param {string=} props.activeIconColor: The color of the icon when speaking is active
- * @param {number} props.shrink: The parameter to shrink the text. Default: 1
- * @param {number} props.fontSize: The parameter to change the font size of the text. Default: 18
- * @param {string} props.fontStyle: The parameter to change the font style of the text. Default: 'normal' ('italic')
- * @param {object=} props.style: The parameter to change the font style of the text. Default: 'normal' ('italic')
- * @param {string} props.align Defines the vertical alignment of the button and text
- * @param {number} props.paddingTop: Determines the top padding of the text. Default: 8
- * @param {number} props.speed: Determines the speed rate of the voice to speak. Default: 1.0
- * @param {string|number} props.id: The parameter to identify the buttons
+ * @param props.activeIconColor {string=} The color of the icon when speaking is active
+ * @param props.shrink {number} The parameter to shrink the text. Default: 1
+ * @param props.fontSize {number} The parameter to change the font size of the text. Default: 18
+ * @param props.fontStyle {string} The parameter to change the font style of the text. Default: 'normal' ('italic')
+ * @param props.style {object=} The parameter to change the font style of the text. Default: 'normal' ('italic')
+ * @param props.align {string} Defines the vertical alignment of the button and text
+ * @param props.paddingTop {number} Determines the top padding of the text. Default: 8
+ * @param props.speed {number} Determines the speed rate of the voice to speak. Default: 1.0
+ * @param props.buttonRef {Component?} optional ref passed to connect to button
+ * @param props.id {string|number} The parameter to identify the buttons
* @returns {JSX.Element}
* @component
*/
@@ -285,6 +287,7 @@ Source: components/Tts.js
}
return (
<Button
+ ref={props.buttonRef}
accessibilityRole='button'
testID={props.testId}
containerStyle={ttsContainerStyle}
@@ -507,13 +510,13 @@ Source: components/Tts.js
- Classes Global
+ Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_factories_UnitContentElementFactory.js.html b/docs/api/app/components_factories_UnitContentElementFactory.js.html
index de4402c9..86fb9bc0 100644
--- a/docs/api/app/components_factories_UnitContentElementFactory.js.html
+++ b/docs/api/app/components_factories_UnitContentElementFactory.js.html
@@ -117,13 +117,13 @@
@@ -108,13 +129,13 @@ Source: components/renderer/media/ImageRenderer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_renderer_text_Markdown.js.html b/docs/api/app/components_renderer_text_Markdown.js.html
index fba3cdba..d50cb5de 100644
--- a/docs/api/app/components_renderer_text_Markdown.js.html
+++ b/docs/api/app/components_renderer_text_Markdown.js.html
@@ -68,13 +68,13 @@ Source: components/renderer/text/Markdown.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/components_renderer_text_PlainTextRenderer.js.html b/docs/api/app/components_renderer_text_PlainTextRenderer.js.html
index 0caf3b33..736586bd 100644
--- a/docs/api/app/components_renderer_text_PlainTextRenderer.js.html
+++ b/docs/api/app/components_renderer_text_PlainTextRenderer.js.html
@@ -60,13 +60,13 @@ Source: components/renderer/text/PlainTextRenderer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/constants_Colors.js.html b/docs/api/app/constants_Colors.js.html
index 0b5f43cb..52483683 100644
--- a/docs/api/app/constants_Colors.js.html
+++ b/docs/api/app/constants_Colors.js.html
@@ -57,13 +57,13 @@ Source: constants/Colors.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/constants_Layout.js.html b/docs/api/app/constants_Layout.js.html
index 92c285d8..0cee7db4 100644
--- a/docs/api/app/constants_Layout.js.html
+++ b/docs/api/app/constants_Layout.js.html
@@ -29,7 +29,6 @@ Source: constants/Layout.js
import { Colors } from './Colors'
import Constants from 'expo-constants'
import { Dimensions, PixelRatio } from 'react-native'
-import { fontIsLoaded } from '../utils/fontIsLoaded'
const window = Dimensions.get('window')
const screen = Dimensions.get('screen')
@@ -76,6 +75,16 @@ Source: constants/Layout.js
justifyContent: 'space-around'
})
+Layout.row = () => {
+ return {
+ flexDirection: 'row',
+ // alignItems: 'stretch',
+ // justifyItems: 'stretch',
+ justifyContent: 'space-around',
+ alignItems: 'center'
+ }
+}
+
Layout.content = () => {
return {
padding: 20
@@ -87,7 +96,7 @@ Source: constants/Layout.js
* components.
*/
Layout.input = () => {
- const style = {
+ return {
padding: 5,
fontSize: Layout.fontSize() / Layout.fontScale(),
color: Colors.secondary,
@@ -96,32 +105,22 @@ Source: constants/Layout.js
borderTopLeftRadius: 4,
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
- borderBottomLeftRadius: 4
- }
-
- if (fontIsLoaded(SEMICOLON_FONT)) {
- style.fontFamily = SEMICOLON_FONT
+ borderBottomLeftRadius: 4,
+ fontFamily: SEMICOLON_FONT
}
-
- return style
}
Layout.defaultFont = () => {
- const style = {
+ return {
color: Colors.secondary,
fontSize: Layout.fontSize() / Layout.fontScale(),
lineHeight: 28,
fontStyle: 'normal',
fontWeight: 'normal',
padding: 0,
- margin: 0
+ margin: 0,
+ fontFamily: SEMICOLON_FONT
}
-
- if (fontIsLoaded(SEMICOLON_FONT)) {
- style.fontFamily = SEMICOLON_FONT
- }
-
- return style
}
Layout.borderRadius = () => 15
@@ -164,13 +163,13 @@ Source: constants/Layout.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/contexts_UserProgress.js.html b/docs/api/app/contexts_UserProgress.js.html
index 04b66f7f..8128c8db 100644
--- a/docs/api/app/contexts_UserProgress.js.html
+++ b/docs/api/app/contexts_UserProgress.js.html
@@ -159,13 +159,13 @@ Source: contexts/UserProgress.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/contexts_collectionNotInitialized.js.html b/docs/api/app/contexts_collectionNotInitialized.js.html
index 29339500..739efd0d 100644
--- a/docs/api/app/contexts_collectionNotInitialized.js.html
+++ b/docs/api/app/contexts_collectionNotInitialized.js.html
@@ -45,13 +45,13 @@ Source: contexts/collectionNotInitialized.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/dev_DeveloperScreen.js.html b/docs/api/app/dev_DeveloperScreen.js.html
index bf1042f9..94fd04f5 100644
--- a/docs/api/app/dev_DeveloperScreen.js.html
+++ b/docs/api/app/dev_DeveloperScreen.js.html
@@ -286,13 +286,13 @@ Source: dev/DeveloperScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/dev_loadDevData.js.html b/docs/api/app/dev_loadDevData.js.html
index cd06afb3..82f5a052 100644
--- a/docs/api/app/dev_loadDevData.js.html
+++ b/docs/api/app/dev_loadDevData.js.html
@@ -99,13 +99,13 @@ Source: dev/loadDevData.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/dev_resetSyncData.js.html b/docs/api/app/dev_resetSyncData.js.html
index c9d31e0f..c83044bc 100644
--- a/docs/api/app/dev_resetSyncData.js.html
+++ b/docs/api/app/dev_resetSyncData.js.html
@@ -54,13 +54,13 @@ Source: dev/resetSyncData.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/env_Config.js.html b/docs/api/app/env_Config.js.html
index 46ca34d8..17b89383 100644
--- a/docs/api/app/env_Config.js.html
+++ b/docs/api/app/env_Config.js.html
@@ -27,7 +27,7 @@ Source: env/Config.js
/* global __DEV__ */
-import settings from '../settings.json'
+import settings from '../../settings/settings.json' // check metro.config.js
const { appToken, backend, content, log, debug, isDevelopment, isDeveloperRelease } = settings
@@ -57,6 +57,7 @@ Source: env/Config.js
* This is useful, if you want to debug positioning, padding and margin or flex layout.
*/
Config.debug.layoutBorders = debug.layoutBorders
+Config.debug.connection = debug.connection
Config.log = {}
Config.log.level = log.level
@@ -137,13 +138,13 @@ Source: env/Config.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/env_loadSettingsFromUserProfile.js.html b/docs/api/app/env_loadSettingsFromUserProfile.js.html
index f8c9d4c8..ca506c8e 100644
--- a/docs/api/app/env_loadSettingsFromUserProfile.js.html
+++ b/docs/api/app/env_loadSettingsFromUserProfile.js.html
@@ -69,13 +69,13 @@ Source: env/loadSettingsFromUserProfile.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/errors_AuthenticationError.js.html b/docs/api/app/errors_AuthenticationError.js.html
index d6c7a5a8..0a163831 100644
--- a/docs/api/app/errors_AuthenticationError.js.html
+++ b/docs/api/app/errors_AuthenticationError.js.html
@@ -59,13 +59,13 @@ Source: errors/AuthenticationError.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/errors_ConnectionError.js.html b/docs/api/app/errors_ConnectionError.js.html
index 3880d3fa..2be89cb6 100644
--- a/docs/api/app/errors_ConnectionError.js.html
+++ b/docs/api/app/errors_ConnectionError.js.html
@@ -65,13 +65,13 @@ Source: errors/ConnectionError.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/errors_MeteorError.js.html b/docs/api/app/errors_MeteorError.js.html
index 16477589..4835bc5b 100644
--- a/docs/api/app/errors_MeteorError.js.html
+++ b/docs/api/app/errors_MeteorError.js.html
@@ -100,13 +100,13 @@ Source: errors/MeteorError.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/global.html b/docs/api/app/global.html
index b6187abd..9578f9a2 100644
--- a/docs/api/app/global.html
+++ b/docs/api/app/global.html
@@ -336,7 +336,7 @@ (constant) AppSource:
@@ -1113,7 +1113,7 @@ (constant)
Source:
@@ -1624,7 +1624,7 @@ (constant)
Source:
@@ -1765,7 +1765,7 @@ (constant) L
Source:
@@ -2012,7 +2012,7 @@ (constant) M
Source:
@@ -2328,7 +2328,7 @@ (constant) Source:
@@ -2591,7 +2591,7 @@ Type:
Source:
@@ -2781,7 +2781,7 @@ (constant) Source:
@@ -3035,7 +3035,7 @@ Properties:
Source:
@@ -3098,7 +3098,7 @@ (con
Source:
@@ -3165,7 +3165,7 @@ (constant) Source:
@@ -5599,6 +5599,68 @@ (constant) (constant) loadHomeData
+
+
+
+
+
+
Loads the available fields for the home screen.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(constant) loadMapData
@@ -6465,7 +6527,7 @@ (constant) Source:
@@ -6597,7 +6659,7 @@ (constant) use
Source:
@@ -6691,6 +6753,70 @@ (constant) us
+(constant) useRefresh
+
+
+
+
+
+
A little helper hook, that can be used to mediate between
+{useDocs} and {BaseScreen} in order to implement a
+page-refresh functionality.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(constant) validateSettingsSchema
@@ -7872,7 +7998,7 @@ Properties
Source:
@@ -8552,6 +8678,39 @@ Properties
+
+
+
+
+
+
+
+ onRefresh
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+
+ <nullable>
+
+
+
+
+
+
+
+
@@ -8600,7 +8759,7 @@ Properties
Source:
@@ -8654,7 +8813,7 @@ Returns:
- TtsComponent() → {JSX.Element}
+ TtsComponent(props) → {JSX.Element}
@@ -8686,6 +8845,48 @@ Parameters:
Type
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ props
+
+
+
+
+
+object
+
+
+
+
+
+
+
+
+
+
+ Properties
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
Attributes
@@ -8700,7 +8901,7 @@ Parameters:
- props.text:
+ text
@@ -8731,7 +8932,7 @@ Parameters:
- props.ttsText:
+ ttsText
@@ -8762,7 +8963,7 @@ Parameters:
- props.dontShowText:
+ dontShowText
@@ -8793,7 +8994,7 @@ Parameters:
- props.smallButton:
+ smallButton
@@ -8824,7 +9025,7 @@ Parameters:
- props.block:
+ block
@@ -8857,7 +9058,7 @@ Parameters:
- props.asButton:
+ asButton
@@ -8890,7 +9091,7 @@ Parameters:
- props.disabled:
+ disabled
@@ -8923,7 +9124,7 @@ Parameters:
- props.color:
+ color
@@ -8957,7 +9158,7 @@ Parameters:
- props.iconColor:
+ iconColor
@@ -8991,7 +9192,7 @@ Parameters:
- props.activeIconColor:
+ activeIconColor
@@ -9024,7 +9225,7 @@ Parameters:
- props.shrink:
+ shrink
@@ -9055,7 +9256,7 @@ Parameters:
- props.fontSize:
+ fontSize
@@ -9086,7 +9287,7 @@ Parameters:
- props.fontStyle:
+ fontStyle
@@ -9117,7 +9318,7 @@ Parameters:
- props.style:
+ style
@@ -9150,7 +9351,7 @@ Parameters:
- props.align
+ align
@@ -9181,7 +9382,7 @@ Parameters:
- props.paddingTop:
+ paddingTop
@@ -9212,7 +9413,7 @@ Parameters:
- props.speed:
+ speed
@@ -9243,7 +9444,40 @@ Parameters:
- props.id:
+ buttonRef
+
+
+
+
+
+Component
+
+
+
+
+
+
+
+
+
+
+ <nullable>
+
+
+
+
+
+
+
+
+ optional ref passed to connect to button
+
+
+
+
+
+
+ id
@@ -9277,6 +9511,13 @@ Parameters:
+
+
+
+
+
+
+
@@ -9311,7 +9552,7 @@ Parameters:
Source:
@@ -9501,7 +9742,7 @@ Parameters:
Source:
@@ -9690,7 +9931,7 @@ Parameters:
Source:
@@ -9720,6 +9961,501 @@ Parameters:
+
+
+
+
+
+
+ signUp(termsAndConditionsIsCheckednullable , voicenullable , speednullable , onError, onSuccess) → {Promise.<void>}
+
+
+
+
+
+
+
+
Registers a new user account on the backend server
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+ Attributes
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ termsAndConditionsIsChecked
+
+
+
+
+
+boolean
+
+
+
+
+
+
+
+
+
+
+ <nullable>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ voice
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+ <nullable>
+
+
+
+
+
+
+
+
+ identifier of the selected voice
+
+
+
+
+
+
+ speed
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+ <nullable>
+
+
+
+
+
+
+
+
+ speed value for voice
+
+
+
+
+
+
+ onError
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ onSuccess
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+
+
+ Type
+
+
+
+Promise.<void>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ usePath(from, to, width, height) → {string}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ from
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ to
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ width
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ height
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+
+
+ Type
+
+
+
+string
+
+
+
+
+
+
+
+
+
+
+
@@ -9736,13 +10472,13 @@ Parameters:
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/hooks_useBackHandler.js.html b/docs/api/app/hooks_useBackHandler.js.html
index 40ed632a..67d59aff 100644
--- a/docs/api/app/hooks_useBackHandler.js.html
+++ b/docs/api/app/hooks_useBackHandler.js.html
@@ -52,13 +52,13 @@ Source: hooks/useBackHandler.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/hooks_useConnection.js.html b/docs/api/app/hooks_useConnection.js.html
index 6cd94685..4296808d 100644
--- a/docs/api/app/hooks_useConnection.js.html
+++ b/docs/api/app/hooks_useConnection.js.html
@@ -26,70 +26,122 @@ Source: hooks/useConnection.js
- import { useState } from 'react'
+ import { useEffect, useState } from 'react'
import Meteor from '@meteorrn/core'
import * as SecureStore from 'expo-secure-store'
import { Config } from '../env/Config'
import { Log } from '../infrastructure/Log'
-
-const log = Log.create('useConnection')
-
-const connect = (() => {
- let started = false
-
- return () => {
- if (started) {
- return false
- }
- else {
- started = true
- }
-
- // get detailed info about internals
- Meteor.enableVerbose()
-
- log('connect Meteor to backend at', Config.backend.url)
- log('url for reachability test is', Config.backend.reachabilityUrl)
-
- // connect with Meteor and use a secure store
- // to persist our received login token, so it's encrypted
- // and only readable for this very app
- // read more at: https://docs.expo.dev/versions/latest/sdk/securestore/
- Meteor.connect(Config.backend.url, {
- AsyncStorage: {
- getItem: SecureStore.getItemAsync,
- setItem: SecureStore.setItemAsync,
- removeItem: SecureStore.deleteItemAsync
- },
- autoConnect: true,
- autoReconnect: true,
- reconnectInterval: 500,
- reachabilityUrl: Config.backend.reachabilityUrl
- })
-
- return false
- }
-})()
+import { AppState } from 'react-native'
+import { useNetInfo } from '@react-native-community/netinfo'
+
+Meteor.enableVerbose()
+
+// connect with Meteor and use a secure store
+// to persist our received login token, so it's encrypted
+// and only readable for this very app
+// read more at: https://docs.expo.dev/versions/latest/sdk/securestore/
+Meteor.connect(Config.backend.url, {
+ AsyncStorage: {
+ getItem: SecureStore.getItemAsync,
+ setItem: SecureStore.setItemAsync,
+ removeItem: SecureStore.deleteItemAsync
+ },
+ autoConnect: false,
+ autoReconnect: true,
+ reconnectInterval: 3000,
+ NetInfo: null
+})
/**
* Hook that handle auto-reconnect and updates state accordingly.
* @return {{connected: boolean|null}}
*/
export const useConnection = () => {
- const [connected, setConnected] = useState(() => connect())
- const status = Meteor.useTracker(() => Meteor.status())
+ const appState = useAppState()
+ const [connected, setConnected] = useState(null)
+ const [status, setStatus] = useState('connecting')
+ const { isConnected } = useNetInfo({
+ reachabilityUrl: Config.backend.reachabilityUrl,
+ reachabilityTest: async (response) => response.status === 204,
+ reachabilityLongTimeout: 15 * 1000, // 15s
+ reachabilityShortTimeout: 5 * 1000, // 5s
+ reachabilityRequestTimeout: 15 * 1000, // 15s
+ reachabilityShouldRun: () => appState === 'active' && !connected,
+ shouldFetchWiFiSSID: true, // met iOS requirements to get SSID
+ useNativeReachability: false
+ })
+
+ const www = !!isConnected
+ const backend = status === 'connected'
+
+ useEffect(() => {
+ const Data = Meteor.getData()
+ const updateConnected = (backendConnected) => {
+ if (backendConnected && connected !== true) {
+ log('set connected=true')
+ setConnected(true)
+ }
+
+ if (connected !== false && !backendConnected) {
+ log('set connected=false')
+ setConnected(false)
+ }
+ }
+ Data.ddp.on('error', e => {
+ // Log.error(e)
+ log('DDP on error')
+ Log.info(e.message)
+ })
- if (status.connected && !connected) {
- log(Config.backend.url, 'connected', status)
- setConnected(true)
- }
+ Data.ddp.on('connected', () => {
+ log('DDP on connected')
+ setStatus('connected')
+ updateConnected(true)
+ })
+
+ Data.ddp.on('disconnected', () => {
+ log('DDP on disconnected')
+ setStatus('connecting')
+ updateConnected(false)
+ })
+
+ Meteor.reconnect()
- if (connected && !status.connected) {
- log(Config.backend.url, 'disconnected', status)
- setConnected(false)
+ return () => {
+ Data.ddp.off('error')
+ Data.ddp.off('connected')
+ Data.ddp.off('disconnected')
+ }
+ }, [])
+
+ useEffect(() => {
+ if ((isConnected && appState === 'active') && connected === false) {
+ log('connect to server')
+ Meteor.getData().ddp.connect()
+ }
+
+ if ((!isConnected || appState === 'background') && connected === true) {
+ log('disconnect from server')
+ Meteor.getData().ddp.disconnect()
+ }
+ }, [appState, connected, isConnected])
+
+ return {
+ connected,
+ www,
+ backend
}
+}
+
+const log = Log.create('useConnection')
- return { connected }
+const useAppState = () => {
+ const [appState, setAppState] = useState(AppState.currentState)
+ useEffect(() => {
+ const listener = AppState.addEventListener('change', newState => setAppState(newState))
+ return () => listener.remove()
+ }, [])
+ return appState
}
@@ -101,13 +153,13 @@ Source: hooks/useConnection.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/hooks_useLogin.js.html b/docs/api/app/hooks_useLogin.js.html
index 8780d4fb..ebcac72a 100644
--- a/docs/api/app/hooks_useLogin.js.html
+++ b/docs/api/app/hooks_useLogin.js.html
@@ -29,7 +29,6 @@ Source: hooks/useLogin.js
import { useReducer, useEffect, useMemo } from 'react'
import Meteor from '@meteorrn/core'
import { loadSettingsFromUserProfile } from '../env/loadSettingsFromUserProfile'
-import { useConnection } from './useConnection'
import { Config } from '../env/Config'
import { getDeviceData } from '../analystics/getDeviceData'
import { Log } from '../infrastructure/Log'
@@ -39,6 +38,10 @@ Source: hooks/useLogin.js
const initialState = {
isLoading: true,
isSignout: false,
+ /**
+ * we will shortly remove this
+ * @deprecated
+ */
isDeleted: false,
userToken: null
}
@@ -82,9 +85,6 @@ Source: hooks/useLogin.js
}
}
-/** @private */
-const Data = Meteor.getData()
-
/**
* Provides a state and authentication context for components to decide, whether
* the user is authenticated and also to run several authentication actions.
@@ -109,19 +109,22 @@ Source: hooks/useLogin.js
* authContext: object
* }}
*/
-export const useLogin = () => {
- const { connected } = useConnection()
+export const useLogin = ({ connection }) => {
+ const { connected } = connection
const [state, dispatch] = useReducer(reducer, initialState, undefined)
+ const user = Meteor.useTracker(() => Meteor.user())
- Meteor.useTracker(() => {
- if (!connected) { return }
- const user = Meteor.user()
+ useEffect(() => {
+ if (!connected || !user || state.profileLoaded) { return }
- if (!state.profileLoaded && user) {
+ try {
loadSettingsFromUserProfile(user)
- dispatch({ type: 'PROFILE_LOADED' })
}
- }, [connected])
+ catch (e) {
+ ErrorReporter.send({ error: e }).catch(Log.error)
+ }
+ dispatch({ type: 'PROFILE_LOADED' })
+ }, [connected, user])
// Case 1: restore token already exists
// MeteorRN loads the token on connection automatically,
@@ -144,8 +147,8 @@ Source: hooks/useLogin.js
// Note, that 'onLogin' is only fired, when the
// package has successfully restored the login via token!
else {
- Data.on('onLogin', handleOnLogin)
- return () => Data.off('onLogin', handleOnLogin)
+ Meteor.getData().on('onLogin', handleOnLogin)
+ return () => Meteor.getData().off('onLogin', handleOnLogin)
}
}, [connected])
@@ -162,8 +165,18 @@ Source: hooks/useLogin.js
dispatch({ type: 'SIGN_OUT' })
})
},
- signUp: async ({ voice, speed, onError, onSuccess }) => {
- const args = { voice, speed }
+
+ /**
+ * Registers a new user account on the backend server
+ * @param termsAndConditionsIsChecked {boolean?}
+ * @param voice {string?} identifier of the selected voice
+ * @param speed {number?} speed value for voice
+ * @param onError {function}
+ * @param onSuccess {function}
+ * @return {Promise<void>}
+ */
+ signUp: async ({ termsAndConditionsIsChecked, voice, speed, onError, onSuccess }) => {
+ const args = { voice, speed, termsAndConditionsIsChecked }
if (Config.isDeveloperRelease()) {
args.isDev = true
@@ -227,21 +240,29 @@ Source: hooks/useLogin.js
})
},
deleteAccount: ({ onError, onSuccess }) => {
- Meteor.call(Config.methods.deleteUser, {}, (deleteError) => {
+ Meteor.call(Config.methods.deleteUser, {}, async (deleteError) => {
if (deleteError) {
return onError(deleteError)
}
// instead of calling Meteor.logout we
// directly invoke the logout handler
+ if (onSuccess) {
+ try {
+ await onSuccess()
+ }
+ catch (e) {
+ onError(e)
+ }
+ }
+
Meteor.handleLogout()
- onSuccess && onSuccess()
- dispatch({ type: 'DELETE' })
+ dispatch({ type: 'SIGN_OUT' })
})
}
}), [])
- return { state, authContext }
+ return { state, user, authContext }
}
@@ -253,13 +274,13 @@ Source: hooks/useLogin.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/hooks_useRefresh.js.html b/docs/api/app/hooks_useRefresh.js.html
new file mode 100644
index 00000000..2afd0559
--- /dev/null
+++ b/docs/api/app/hooks_useRefresh.js.html
@@ -0,0 +1,66 @@
+
+
+
+
+ JSDoc: Source: hooks/useRefresh.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: hooks/useRefresh.js
+
+
+
+
+
+
+
+
+ import { useCallback, useState } from 'react'
+
+/**
+ * A little helper hook, that can be used to mediate between
+ * {useDocs} and {BaseScreen} in order to implement a
+ * page-refresh functionality.
+ * @return {Array<number, function(): void>}
+ */
+export const useRefresh = () => {
+ const [reload, setReload] = useState(0)
+ const refresh = useCallback(() => {
+ setReload(reload + 1)
+ }, [reload])
+ return [reload, refresh]
+}
+
+
+
+
+
+
+
+
+
+
+ Home Classes Global
+
+
+
+
+
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
+
+
+
+
+
+
diff --git a/docs/api/app/index.html b/docs/api/app/index.html
index 04a0b60a..1097e172 100644
--- a/docs/api/app/index.html
+++ b/docs/api/app/index.html
@@ -44,11 +44,11 @@
About
The lea.online app is a mobile app, developed using React-Native and Meteor.
Get the app
@@ -85,13 +85,13 @@ License
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/infrastructure_Log.js.html b/docs/api/app/infrastructure_Log.js.html
index 84e7cdd2..01a69ac4 100644
--- a/docs/api/app/infrastructure_Log.js.html
+++ b/docs/api/app/infrastructure_Log.js.html
@@ -186,13 +186,13 @@ Source: infrastructure/Log.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/infrastructure_collections_LeaCollection.js.html b/docs/api/app/infrastructure_collections_LeaCollection.js.html
index 7245fd54..fac9c630 100644
--- a/docs/api/app/infrastructure_collections_LeaCollection.js.html
+++ b/docs/api/app/infrastructure_collections_LeaCollection.js.html
@@ -93,13 +93,13 @@ Source: infrastructure/collections/LeaCollection.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/infrastructure_collections_collections.js.html b/docs/api/app/infrastructure_collections_collections.js.html
index 87e551b5..b8468da8 100644
--- a/docs/api/app/infrastructure_collections_collections.js.html
+++ b/docs/api/app/infrastructure_collections_collections.js.html
@@ -67,13 +67,13 @@ Source: infrastructure/collections/collections.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/infrastructure_createCollection.js.html b/docs/api/app/infrastructure_createCollection.js.html
index 67f2335e..1c4103b2 100644
--- a/docs/api/app/infrastructure_createCollection.js.html
+++ b/docs/api/app/infrastructure_createCollection.js.html
@@ -64,13 +64,13 @@ Source: infrastructure/createCollection.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/infrastructure_createRepository.js.html b/docs/api/app/infrastructure_createRepository.js.html
index b3dd820e..d38262d7 100644
--- a/docs/api/app/infrastructure_createRepository.js.html
+++ b/docs/api/app/infrastructure_createRepository.js.html
@@ -58,13 +58,13 @@ Source: infrastructure/createRepository.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/infrastructure_log_InteractionGraph.js.html b/docs/api/app/infrastructure_log_InteractionGraph.js.html
index ba387364..ede3a69f 100644
--- a/docs/api/app/infrastructure_log_InteractionGraph.js.html
+++ b/docs/api/app/infrastructure_log_InteractionGraph.js.html
@@ -166,13 +166,13 @@ Source: infrastructure/log/InteractionGraph.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/infrastructure_sync_Sync.js.html b/docs/api/app/infrastructure_sync_Sync.js.html
index 1581e2fc..9cf89b8c 100644
--- a/docs/api/app/infrastructure_sync_Sync.js.html
+++ b/docs/api/app/infrastructure_sync_Sync.js.html
@@ -244,13 +244,13 @@ Source: infrastructure/sync/Sync.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_choice_ChoiceRenderer.js.html b/docs/api/app/items_choice_ChoiceRenderer.js.html
index 7f308de6..0c20066c 100644
--- a/docs/api/app/items_choice_ChoiceRenderer.js.html
+++ b/docs/api/app/items_choice_ChoiceRenderer.js.html
@@ -208,13 +208,13 @@ Source: items/choice/ChoiceRenderer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_choice_getChoiceEntryScoreColor.js.html b/docs/api/app/items_choice_getChoiceEntryScoreColor.js.html
index e9194729..144fd156 100644
--- a/docs/api/app/items_choice_getChoiceEntryScoreColor.js.html
+++ b/docs/api/app/items_choice_getChoiceEntryScoreColor.js.html
@@ -71,13 +71,13 @@ Source: items/choice/getChoiceEntryScoreColor.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_cloze_Cloze.js.html b/docs/api/app/items_cloze_Cloze.js.html
index 362692c8..b128a677 100644
--- a/docs/api/app/items_cloze_Cloze.js.html
+++ b/docs/api/app/items_cloze_Cloze.js.html
@@ -91,13 +91,13 @@ Source: items/cloze/Cloze.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_cloze_ClozeRenderer.js.html b/docs/api/app/items_cloze_ClozeRenderer.js.html
index a6823c31..816b026d 100644
--- a/docs/api/app/items_cloze_ClozeRenderer.js.html
+++ b/docs/api/app/items_cloze_ClozeRenderer.js.html
@@ -491,13 +491,13 @@ Source: items/cloze/ClozeRenderer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_cloze_ClozeRendererBlank.js.html b/docs/api/app/items_cloze_ClozeRendererBlank.js.html
index 0f45633a..945ae9a9 100644
--- a/docs/api/app/items_cloze_ClozeRendererBlank.js.html
+++ b/docs/api/app/items_cloze_ClozeRendererBlank.js.html
@@ -251,13 +251,13 @@ Source: items/cloze/ClozeRendererBlank.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_cloze_ClozeRendererSelect.js.html b/docs/api/app/items_cloze_ClozeRendererSelect.js.html
index 39e782ec..11fc1f03 100644
--- a/docs/api/app/items_cloze_ClozeRendererSelect.js.html
+++ b/docs/api/app/items_cloze_ClozeRendererSelect.js.html
@@ -311,13 +311,13 @@ Source: items/cloze/ClozeRendererSelect.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_cloze_ClozeTokenizer.js.html b/docs/api/app/items_cloze_ClozeTokenizer.js.html
index b13d4920..9e3e2fbf 100644
--- a/docs/api/app/items_cloze_ClozeTokenizer.js.html
+++ b/docs/api/app/items_cloze_ClozeTokenizer.js.html
@@ -239,13 +239,13 @@ Source: items/cloze/ClozeTokenizer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_cloze_createScoringSummaryForInput.js.html b/docs/api/app/items_cloze_createScoringSummaryForInput.js.html
index 4fb87a8f..0b0865f4 100644
--- a/docs/api/app/items_cloze_createScoringSummaryForInput.js.html
+++ b/docs/api/app/items_cloze_createScoringSummaryForInput.js.html
@@ -53,7 +53,7 @@ Source: items/cloze/createScoringSummaryForInput.js
index: itemIndex,
score: 0, // computed average
color: undefined,
- actual: actual,
+ actual,
entries: []
}
@@ -86,13 +86,13 @@ Source: items/cloze/createScoringSummaryForInput.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_connect_ConnectItemRenderer.js.html b/docs/api/app/items_connect_ConnectItemRenderer.js.html
index 0578685e..9f832a37 100644
--- a/docs/api/app/items_connect_ConnectItemRenderer.js.html
+++ b/docs/api/app/items_connect_ConnectItemRenderer.js.html
@@ -26,19 +26,22 @@ Source: items/connect/ConnectItemRenderer.js
- import React, { useRef, useState, useEffect } from 'react'
+ import React, { useRef, useState, useEffect, useCallback } from 'react'
import {
- ActivityIndicator,
Pressable,
View
} from 'react-native'
import { createStyleSheet } from '../../styles/createStyleSheet'
import { LeaText } from '../../components/LeaText'
import { Colors } from '../../constants/Colors'
-import { Svg, Path, Circle } from 'react-native-svg'
+import { Svg, Circle, G } from 'react-native-svg'
import { UndefinedScore } from '../../scoring/UndefinedScore'
import { clearObject } from '../../utils/object/clearObject'
import { ImageRenderer } from '../../components/renderer/media/ImageRenderer'
+import { Log } from '../../infrastructure/Log'
+import { DashedLine } from '../../components/animated/DashedLine'
+
+const debug = Log.create('ConnectItemRenderer', 'debug')
/**
* Renders a connect item, where users have to connect
@@ -66,7 +69,12 @@ Source: items/connect/ConnectItemRenderer.js
const [selected, setSelected] = useState({})
const [highlighted, setHighlighted] = useState(initialHighlighted())
const [compared, setCompared] = useState(initialCompared())
- const svgContainer = useRef({}).current
+ const svgContainer = useRef({
+ x: 0,
+ y: 0,
+ w: 100,
+ h: 100
+ }).current
const leftDots = useRef({}).current
const rightDots = useRef({}).current
const [active, setActive] = useState(null)
@@ -99,6 +107,13 @@ Source: items/connect/ConnectItemRenderer.js
data: props
})
}, [props.contentId])
+ const updateSvgContainer = ({ x, y, w, h }) => {
+ debug('SVG Container set layout', { x, y, w, h })
+ svgContainer.x = x
+ svgContainer.y = y
+ svgContainer.w = w
+ svgContainer.h = h
+ }
// on showCorrectResponse, do:
// compare responses with the correct responses when
@@ -177,34 +192,33 @@ Source: items/connect/ConnectItemRenderer.js
// to get the correct coordinates;
// event.nativeEvent.layout would only give
// us the local coordinates
+
event.target.measureInWindow((x, y, w, h) => {
- svgContainer.x = x
- svgContainer.y = y
- svgContainer.w = w
- svgContainer.h = h
+ updateSvgContainer({ x, y, w, h })
})
}
// measures the position of the invisible dots,
// which are used to get the start and end points
// to draw the connections
- const onDotLayout = (event, id, isLeft) => {
- event.target.measureInWindow((x, y) => {
- const target = isLeft
- ? leftDots
- : rightDots
- target[id] = { x, y }
-
- if (
- Object.keys(leftDots).length === props.value.left.length &&
- Object.keys(rightDots).length === props.value.right.length
- ) {
- setTimeout(() => setComplete(true), 500)
- }
+ const onDotLayout = useCallback((event, id, isLeft) => {
+ const stamp = Date.now()
+ const target = isLeft
+ ? leftDots
+ : rightDots
+
+ debug('dot layout', isLeft ? 'L' : 'R', id, event.nativeEvent.layout, event.target.key)
+ event.target.measureInWindow((x, y, w, h) => {
+ const centerX = isLeft ? w + 5 : x
+ const centerY = y + (h / 2)
+ debug('dot measure', isLeft ? 'L' : 'R', id, { x, y, w, h, centerX, centerY }, stamp)
+
+ target[id] = { x: centerX, y: centerY }
})
- }
+ }, [])
- const onPressLeft = ({ id }) => {
+ const onPressLeft = useCallback(({ id }) => {
+ if (!complete) { setComplete(true) }
if (props.showCorrectResponse) {
return updateHighlighted(id, 'left')
}
@@ -215,7 +229,7 @@ Source: items/connect/ConnectItemRenderer.js
else {
setActive(id)
}
- }
+ }, [active, props.showCorrectResponse])
const onPressRight = ({ id }) => {
if (props.showCorrectResponse) {
@@ -276,6 +290,8 @@ Source: items/connect/ConnectItemRenderer.js
data: props
})
+ if (!complete) { setComplete(true) }
+
setSelected(current)
setActive(null)
}
@@ -337,10 +353,9 @@ Source: items/connect/ConnectItemRenderer.js
const renderLeftElements = () => {
return props.value.left.map(({ text, image }, index) => {
- const nodeId = `left-${index}`
+ const nodeStyle = [styles.node]
const isActive = active === index && !props.showCorrectResponse
const isSelected = index in selected && !props.showCorrectResponse
- const nodeStyle = [styles.node]
if (isSelected) {
nodeStyle.push(styles.nodeSelected)
@@ -357,10 +372,11 @@ Source: items/connect/ConnectItemRenderer.js
return (
<Pressable
- accessibilityRole='button'
- key={nodeId}
+ accessibilityRole="button"
+ key={`left-${index}`}
style={styles.nodeContainer}
onPress={() => onPressLeft({ id: index })}
+ onLayout={e => !complete && onDotLayout(e, index, true)}
>
<View style={nodeStyle}>
{
@@ -369,10 +385,6 @@ Source: items/connect/ConnectItemRenderer.js
: renderText({ isSelected, text })
}
</View>
- <View
- style={styles.dot}
- onLayout={(event) => onDotLayout(event, index, true)}
- />
</Pressable>
)
})
@@ -380,8 +392,7 @@ Source: items/connect/ConnectItemRenderer.js
const renderLines = () => {
const allLines = []
-
- const transformToLine = color => key => {
+ const transformToLine = (color, responseType) => key => {
const [left, right] = key.split(',')
const x1 = leftDots[left].x
const y1 = leftDots[left].y
@@ -393,7 +404,7 @@ Source: items/connect/ConnectItemRenderer.js
opacity = getHighlightedStyle({ index: key, type: 'line' })
}
- allLines.push({ x1, y1, x2, y2, color, opacity })
+ allLines.push({ x1, y1, x2, y2, color, opacity, responseType })
}
if (props.showCorrectResponse) {
@@ -411,40 +422,31 @@ Source: items/connect/ConnectItemRenderer.js
const y2 = rightDots[right].y
const color = props.dimensionColor
const opacity = 1.0
-
allLines.push({ x1, y1, x2, y2, color, opacity })
})
})
}
return allLines.map((position, index) => {
- const itemKey = `svg-${index}`
+ const lineKey = `svg-line-${index}`
return (
- <Svg
- key={itemKey} style={styles.svg} width={svgContainer.w} height={svgContainer.h}
- viewBox={`${svgContainer.x} ${svgContainer.y} ${svgContainer.w} ${svgContainer.h}`}
- >
- <Path
- strokeWidth='4'
- stroke={position.color}
- strokeOpacity={position.opacity}
- d={`M ${position.x1} ${position.y1} L ${position.x2} ${position.y2}`}
- />
- <Circle
- x={position.x2}
- y={position.y2}
- r={10}
- fill={position.color}
- fillOpacity={position.opacity}
- />
- </Svg>
+ <Connection
+ key={lineKey}
+ invert={true}
+ width="4"
+ color={position.color}
+ opacity={position.opacity}
+ startX={position.x1}
+ startY={position.y1}
+ endX={position.x2}
+ endY={position.y2}
+ />
)
})
}
const renderRightElements = () => {
return props.value.right.map(({ text, image }, index) => {
- const nodeId = `right-${index}`
const nodeStyle = [styles.node]
const isSelected = (
!props.showCorrectResponse &&
@@ -462,15 +464,12 @@ Source: items/connect/ConnectItemRenderer.js
return (
<Pressable
- accessibilityRole='button'
- key={nodeId}
+ accessibilityRole="button"
+ key={`right-${index}`}
style={styles.nodeContainer}
onPress={() => onPressRight({ id: index })}
+ onLayout={e => !complete && onDotLayout(e, index, false)}
>
- <View
- style={styles.dot}
- onLayout={(event) => onDotLayout(event, index, false)}
- />
<View style={nodeStyle}>
{
image
@@ -485,14 +484,19 @@ Source: items/connect/ConnectItemRenderer.js
return (
<View style={[styles.overlay, props.style]} onLayout={onContainerLayout}>
- {renderLines()}
+ <Svg
+ style={styles.svg}
+ width={svgContainer.w}
+ height={svgContainer.h}
+ viewBox={`${svgContainer.x} ${svgContainer.y} ${svgContainer.w} ${svgContainer.h}`}
+ >
+ {renderLines()}
+ </Svg>
<View style={styles.container}>
<View style={styles.leftContainer}>
{renderLeftElements()}
</View>
- <View style={styles.centerContainer}>
- {!complete && <ActivityIndicator color={props.dimensionColor} />}
- </View>
+ <View style={styles.centerContainer}/>
<View style={styles.rightContainer}>
{renderRightElements()}
</View>
@@ -501,6 +505,35 @@ Source: items/connect/ConnectItemRenderer.js
)
}
+const Connection = React.memo(props => {
+ const path = `M ${props.startX} ${props.startY} L ${props.endX} ${props.endY}`
+ return (
+ <G>
+ <Circle
+ x={props.startX}
+ y={props.startY}
+ r={5}
+ fill={props.color}
+ fillOpacity={props.opacity}
+ />
+ <DashedLine
+ invert={props.invert}
+ width={props.width}
+ color={props.color}
+ opacity={props.opacity}
+ path={path}
+ />
+ <Circle
+ x={props.endX}
+ y={props.endY}
+ r={5}
+ fill={props.color}
+ fillOpacity={props.opacity}
+ />
+ </G>
+ )
+})
+
const renderText = ({ isSelected, text }) => (
<LeaText
fitSize
@@ -540,7 +573,6 @@ Source: items/connect/ConnectItemRenderer.js
overlay: {
marginTop: 25,
borderColor: '#ff0'
-
},
svgContainer: {
borderColor: '#0f0'
@@ -569,7 +601,7 @@ Source: items/connect/ConnectItemRenderer.js
},
centerContainer: {
flex: 1,
- maxWidth: '15%',
+ maxWidth: '33%',
flexDirection: 'column',
flexGrow: 1,
alignItems: 'center',
@@ -581,36 +613,28 @@ Source: items/connect/ConnectItemRenderer.js
justifyContent: 'center'
},
node: {
- paddingTop: 10,
- paddingBottom: 10,
+ marginTop: 10,
+ marginBottom: 10,
flexGrow: 1,
- alignItems: 'stretch',
+ alignItems: 'center',
justifyContent: 'center',
- backgroundColor: Colors.transparent,
+ backgroundColor: Colors.white,
borderColor: Colors.secondary,
+ borderWidth: 1,
borderRadius: 5
},
nodeSelected: {
backgroundColor: Colors.secondary,
borderColor: Colors.secondary
},
- highlightActive: {
-
- },
+ highlightActive: {},
highlightPassive: {
opacity: 0.1
},
- dot: {
- width: 5,
- height: 5,
- backgroundColor: Colors.transparent,
- borderRadius: 15
- },
textSelected: {
color: Colors.white
},
- text: {
- },
+ text: {},
textElement: {},
image: {
width: '100%'
@@ -629,13 +653,13 @@ Source: items/connect/ConnectItemRenderer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_shared_getCompareValuesForSelectableItems.js.html b/docs/api/app/items_shared_getCompareValuesForSelectableItems.js.html
index 660691ab..69d5b4e6 100644
--- a/docs/api/app/items_shared_getCompareValuesForSelectableItems.js.html
+++ b/docs/api/app/items_shared_getCompareValuesForSelectableItems.js.html
@@ -70,13 +70,13 @@ Source: items/shared/getCompareValuesForSelectableItems.j
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_utils_CompareState.js.html b/docs/api/app/items_utils_CompareState.js.html
index 7d17f842..3429062f 100644
--- a/docs/api/app/items_utils_CompareState.js.html
+++ b/docs/api/app/items_utils_CompareState.js.html
@@ -82,13 +82,13 @@ Source: items/utils/CompareState.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/items_utils_KeyboardTypes.js.html b/docs/api/app/items_utils_KeyboardTypes.js.html
index 2e9b8886..f65c050a 100644
--- a/docs/api/app/items_utils_KeyboardTypes.js.html
+++ b/docs/api/app/items_utils_KeyboardTypes.js.html
@@ -92,13 +92,13 @@ Source: items/utils/KeyboardTypes.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/meteor_call.js.html b/docs/api/app/meteor_call.js.html
index 45806190..06a61104 100644
--- a/docs/api/app/meteor_call.js.html
+++ b/docs/api/app/meteor_call.js.html
@@ -28,7 +28,7 @@ Source: meteor/call.js
import Meteor from '@meteorrn/core'
import { check } from '../schema/check'
-import { ensureConnected } from './ensureConnected'
+
import { MeteorError } from '../errors/MeteorError'
import { Log } from '../infrastructure/Log'
import { createSchema } from '../schema/createSchema'
@@ -67,7 +67,6 @@ Source: meteor/call.js
*/
export const callMeteor = (options) => {
check(options, callMethodSchema)
- ensureConnected()
const {
name,
args = undefined,
@@ -115,21 +114,24 @@ Source: meteor/call.js
* @see {callMeteor}
*/
const call = ({ name, args, prepare, receive }) => {
+ const isConnected = Meteor.status()?.status === 'connected'
const promise = new Promise((resolve, reject) => {
// inform that we are connected and about to call the server
if (typeof prepare === 'function') { prepare() }
- Meteor.call(name, args, (error, result) => {
- // inform that we have received
- // something from the server
- if (typeof receive === 'function') { receive() }
+ Meteor.getData().waitDdpConnected(() => {
+ Meteor.call(name, args, (error, result) => {
+ // inform that we have received
+ // something from the server
+ if (typeof receive === 'function') { receive() }
- if (error) {
- // we convert server responses to MeteorError
- return reject(MeteorError.from(error))
- }
+ if (error) {
+ // we convert server responses to MeteorError
+ return reject(MeteorError.from(error))
+ }
- return resolve(result)
+ return resolve(result)
+ })
})
})
@@ -138,7 +140,12 @@ Source: meteor/call.js
// let the promise race against a timeout to ensure
// our UI remains responsive in case we didn't get any
// response from the server
- return createTimedPromise(promise, options)
+ //
+ // XXX: if we are disconnected then we skip the timeout
+ // as we can't really know whether reconnect will be in time
+ return isConnected
+ ? createTimedPromise(promise, options)
+ : promise
}
const optionalFunction = { type: Function, optional: true }
@@ -168,13 +175,13 @@ Source: meteor/call.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/meteor_ensureConnected.js.html b/docs/api/app/meteor_ensureConnected.js.html
index def8a161..df12f4cb 100644
--- a/docs/api/app/meteor_ensureConnected.js.html
+++ b/docs/api/app/meteor_ensureConnected.js.html
@@ -52,13 +52,13 @@ Source: meteor/ensureConnected.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/meteor_updateUserProfile.js.html b/docs/api/app/meteor_updateUserProfile.js.html
index cfa3c1aa..ba1ed214 100644
--- a/docs/api/app/meteor_updateUserProfile.js.html
+++ b/docs/api/app/meteor_updateUserProfile.js.html
@@ -55,13 +55,13 @@ Source: meteor/updateUserProfile.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/meteor_useDocs.js.html b/docs/api/app/meteor_useDocs.js.html
index 0797473e..e58b85d3 100644
--- a/docs/api/app/meteor_useDocs.js.html
+++ b/docs/api/app/meteor_useDocs.js.html
@@ -27,11 +27,11 @@ Source: meteor/useDocs.js
import { useEffect, useState } from 'react'
-import { InteractionGraph } from '../infrastructure/log/InteractionGraph'
import { ErrorReporter } from '../errors/ErrorReporter'
import { Log } from '../infrastructure/Log'
-
-const MAX_ATTEMPTS = 3
+import { isDefined } from '../utils/object/isDefined'
+import { asyncTimeout } from '../utils/asyncTimeout'
+import { useTranslation } from 'react-i18next'
// TODO move to hooks folder
@@ -50,52 +50,88 @@ Source: meteor/useDocs.js
* @param runArgs {array=} optional run args for the internal useEffect
* @param debug {boolean=} optional boolean flag for debugging
* @param allArgsRequired {boolean=} optional boolean flag to skip loading until all given args are non-null
+ * @param reload {number?} optional count that can invoke a new load cycle, usually incremented when the
+ * @param dataRequired {boolean?} optional flag, leading to throw an error, if fn returns null/undefined
+ * @param maxAttempts {number?} optional count, allows to run fn multiple times before continueing
* @return {{data: undefined, error: undefined, loading: boolean}}
*/
-export const useDocs = ({ fn, runArgs = [], debug = false, allArgsRequired = false }) => {
+export const useDocs = ({
+ fn,
+ runArgs = [],
+ debug = false,
+ allArgsRequired = false,
+ dataRequired = false,
+ reload = 0,
+ maxAttempts = 1,
+ message
+}) => {
+ const { t } = useTranslation()
const [data, setData] = useState()
const [error, setError] = useState()
const [loading, setLoading] = useState(true)
+ const [loadMessage, setLoadMessage] = useState()
+
+ useEffect(() => {
+ if (message) {
+ setLoadMessage(t(message))
+ }
+ }, [message])
useEffect(() => {
if (allArgsRequired && runArgs.some(arg => arg === null)) {
return
}
- let attempts = 0
- const load = async function () {
- try {
- const data = await fn(debug)
- setData(data)
- setLoading(false)
- }
- catch (e) {
- attempts++
+ const loadWrapper = async () => {
+ let error
+ let attempts = 0
+
+ setLoading(true)
+ setError(null)
- if (attempts >= MAX_ATTEMPTS) {
- throw e
+ // enable states to take effect
+ // in consuming components, so users
+ // are aware we are (re-)loading
+ await asyncTimeout(300)
+
+ while (attempts < maxAttempts) {
+ try {
+ return await load()
+ }
+ catch (e) {
+ error = e
}
- else {
- return load()
+ finally {
+ attempts++
}
}
+ error.attempts = attempts
+ throw error
}
- load().catch(e => {
- e.details = { attempts, env: useDocs.name, fn: String(fn) }
- ErrorReporter.send({ error: e }).catch(Log.error)
- InteractionGraph.problem({
- type: 'loadFailed',
- target: useDocs.name,
- error: e,
- details: { attempts }
+ const load = async function () {
+ const result = await fn(debug)
+ if (dataRequired && !isDefined(result)) {
+ throw new Error('errors.noDataReceived')
+ }
+ return result
+ }
+
+ loadWrapper()
+ .then(result => {
+ setData(result)
+ setError(null)
+ })
+ .catch(e => {
+ const { attempts = 1 } = e
+ e.details = { attempts, env: useDocs.name, fn: String(fn) }
+ ErrorReporter.send({ error: e }).catch(Log.error)
+ setError(e)
})
- setError(e)
- setLoading(false)
- })
- }, runArgs)
+ .finally(() => setLoading(false))
+ }, [...runArgs, reload])
- return { data, error, loading }
+ return { data, error, loading, loadMessage }
}
@@ -107,13 +143,13 @@ Source: meteor/useDocs.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/navigation_MainNavigation.js.html b/docs/api/app/navigation_MainNavigation.js.html
index b9da12fe..2c23cf55 100644
--- a/docs/api/app/navigation_MainNavigation.js.html
+++ b/docs/api/app/navigation_MainNavigation.js.html
@@ -59,6 +59,7 @@ Source: navigation/MainNavigation.js
import { initAppSession } from '../startup/initAppSession'
import { getHeaderOptions } from './getHeaderOptions'
import { LoggingScreen } from '../screens/logging/LoggingScreen'
+import { TTSProfileScreen } from '../screens/profile/tts/TTSProfileScreen'
const { AppSessionProvider } = initAppSession()
@@ -80,19 +81,20 @@ Source: navigation/MainNavigation.js
export const MainNavigation = (props) => {
useKeepAwake()
const { t } = useTranslation()
- const { state, authContext } = useLogin()
+ const { state, authContext } = useLogin({ connection: props.connection })
const { Tts } = useTts()
const { userToken, isSignout, isDeleted } = state
+
const renderTitleTts = text => (
<Tts align='center' fontStyle={styles.titleFont} text={text} />
)
-
const renderScreens = () => {
if (userToken && !isSignout && !isDeleted) {
const headerRight = () => (<ProfileButton route='profile' />)
const mapScreenTitle = t('mapScreen.title')
const profileScreenTitle = t('profileScreen.headerTitle')
const achievementScreenTitle = t('profileScreen.achievements.title')
+ const ttsProfileScreenTitle = t('profileScreen.tts.title')
const screens = [
<Stack.Screen
name='home'
@@ -177,6 +179,21 @@ Source: navigation/MainNavigation.js
headerRight: () => (<></>)
}}
/>,
+ <Stack.Screen
+ name='ttsprofile'
+ key='ttsprofile'
+ component={TTSProfileScreen}
+ options={{
+ ...headerOptions,
+ headerStyle,
+ title: ttsProfileScreenTitle,
+ headerBackVisible: false,
+ headerTitleAlign: 'center',
+ headerLeft: () => (<BackButton icon='arrow-left' />),
+ headerTitle: () => renderTitleTts(ttsProfileScreenTitle),
+ headerRight: () => (<></>)
+ }}
+ />,
<Stack.Screen
name='achievements'
key='achievements'
@@ -379,13 +396,13 @@ Source: navigation/MainNavigation.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/remotes_ContentServer.js.html b/docs/api/app/remotes_ContentServer.js.html
index 4f5c192f..e8e15f60 100644
--- a/docs/api/app/remotes_ContentServer.js.html
+++ b/docs/api/app/remotes_ContentServer.js.html
@@ -57,13 +57,13 @@ Source: remotes/ContentServer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/schema_createSchema.js.html b/docs/api/app/schema_createSchema.js.html
index 6428b024..870f3c30 100644
--- a/docs/api/app/schema_createSchema.js.html
+++ b/docs/api/app/schema_createSchema.js.html
@@ -63,13 +63,13 @@ Source: schema/createSchema.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/schema_validateSettingsSchema.js.html b/docs/api/app/schema_validateSettingsSchema.js.html
index 3b6c062b..ee3115dc 100644
--- a/docs/api/app/schema_validateSettingsSchema.js.html
+++ b/docs/api/app/schema_validateSettingsSchema.js.html
@@ -27,7 +27,7 @@ Source: schema/validateSettingsSchema.js
import { settingsSchema } from '../settingsSchema'
-import settings from '../settings.json'
+import settings from '../../settings/settings.json'
/**
* Validate the settings.json file against the
@@ -46,13 +46,13 @@ Source: schema/validateSettingsSchema.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/scoring_Scoring.js.html b/docs/api/app/scoring_Scoring.js.html
index bb86b1ef..b7b3db3e 100644
--- a/docs/api/app/scoring_Scoring.js.html
+++ b/docs/api/app/scoring_Scoring.js.html
@@ -116,13 +116,13 @@ Source: scoring/Scoring.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/scoring_ScoringTypes.js.html b/docs/api/app/scoring_ScoringTypes.js.html
index d9810cb9..4f022c71 100644
--- a/docs/api/app/scoring_ScoringTypes.js.html
+++ b/docs/api/app/scoring_ScoringTypes.js.html
@@ -68,13 +68,13 @@ Source: scoring/ScoringTypes.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/scoring_getScoring.js.html b/docs/api/app/scoring_getScoring.js.html
index d7a8118f..3ee85377 100644
--- a/docs/api/app/scoring_getScoring.js.html
+++ b/docs/api/app/scoring_getScoring.js.html
@@ -60,13 +60,13 @@ Source: scoring/getScoring.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/scoring_isUndefinedResponse.js.html b/docs/api/app/scoring_isUndefinedResponse.js.html
index 6a84d291..08fcf905 100644
--- a/docs/api/app/scoring_isUndefinedResponse.js.html
+++ b/docs/api/app/scoring_isUndefinedResponse.js.html
@@ -67,13 +67,13 @@ Source: scoring/isUndefinedResponse.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_BaseScreen.js.html b/docs/api/app/screens_BaseScreen.js.html
index cfca018f..9f53099a 100644
--- a/docs/api/app/screens_BaseScreen.js.html
+++ b/docs/api/app/screens_BaseScreen.js.html
@@ -26,12 +26,15 @@ Source: screens/BaseScreen.js
- import React from 'react'
+ import React, { useCallback } from 'react'
import { Loading } from '../components/Loading'
-import { SafeAreaView } from 'react-native'
+import { RefreshControl, SafeAreaView, ScrollView } from 'react-native'
import { ErrorMessage } from '../components/ErrorMessage'
import { LinearProgress } from 'react-native-elements'
import { Colors } from '../constants/Colors'
+import { createStyleSheet } from '../styles/createStyleSheet'
+import { Layout } from '../constants/Layout'
+import { Log } from '../infrastructure/Log'
/**
*
@@ -43,9 +46,23 @@ Source: screens/BaseScreen.js
* @param props.data {*=}
* @param props.progress {number=}
* @param props.loadMessage {string}
+ * @param props.onRefresh {function?}
* @return {JSX.Element}
*/
const RenderScreenBase = (props) => {
+ const [refreshing, setRefreshing] = React.useState(false)
+
+ const onRefresh = useCallback(async () => {
+ setRefreshing(true)
+ try {
+ await props.onRefresh()
+ }
+ catch (e) {
+ Log.error(e)
+ }
+ setRefreshing(false)
+ }, [props.onRefresh])
+
if (props.loading) {
return (
<SafeAreaView style={props.style}>
@@ -58,7 +75,14 @@ Source: screens/BaseScreen.js
if (props.error) {
return (
<SafeAreaView style={props.style}>
- <ErrorMessage error={props.error} />
+ <ScrollView
+ refreshControl={
+ props.onRefresh && <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
+ }
+ contentContainerStyle={styles.scrollContainer}
+ >
+ <ErrorMessage error={props.error} />
+ </ScrollView>
</SafeAreaView>
)
}
@@ -69,7 +93,14 @@ Source: screens/BaseScreen.js
if (loadFailed) {
return (
<SafeAreaView style={props.style}>
- <ErrorMessage error={new Error('screenBase.notData')} />
+ <ScrollView
+ refreshControl={
+ props.onRefresh && <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
+ }
+ contentContainerStyle={styles.scrollContainer}
+ >
+ <ErrorMessage error={new Error('screenBase.notData')} />
+ </ScrollView>
</SafeAreaView>
)
}
@@ -77,6 +108,14 @@ Source: screens/BaseScreen.js
return (<SafeAreaView style={props.style}>{props.children}</SafeAreaView>)
}
+const styles = createStyleSheet({
+ scrollContainer: {
+ ...Layout.container(),
+ flexGrow: 1,
+ flex: 0
+ }
+})
+
const linearProgress = progress => {
if (typeof progress !== 'number') {
return null
@@ -98,13 +137,13 @@ Source: screens/BaseScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_auth_RegistrationScreen.js.html b/docs/api/app/screens_auth_RegistrationScreen.js.html
index 8e9261d5..403b088e 100644
--- a/docs/api/app/screens_auth_RegistrationScreen.js.html
+++ b/docs/api/app/screens_auth_RegistrationScreen.js.html
@@ -37,7 +37,7 @@ Source: screens/auth/RegistrationScreen.js
import { Layout } from '../../constants/Layout'
import { Loading } from '../../components/Loading'
import { InteractionGraph } from '../../infrastructure/log/InteractionGraph'
-
+import { useRoute } from '@react-navigation/native'
/**
* Screen for registering a new user.
* This screen should automatically run without further actions required.
@@ -45,10 +45,11 @@ Source: screens/auth/RegistrationScreen.js
* @component
* @returns {JSX.Element}
*/
-export const RegistrationScreen = () => {
+export const RegistrationScreen = (props) => {
const { t } = useTranslation()
const { Tts } = useTts()
const { user } = useUser()
+ const route = useRoute()
const { signUp } = useContext(AuthContext)
const [error, setError] = useState(null)
@@ -69,7 +70,14 @@ Source: screens/auth/RegistrationScreen.js
type: 'registered'
})
- setTimeout(() => signUp({ voice, speed, onError, onSuccess }), 1000)
+ const { termsAndConditionsIsChecked } = (route ?? {})
+ setTimeout(() => signUp({
+ termsAndConditionsIsChecked,
+ voice,
+ speed,
+ onError,
+ onSuccess
+ }), 1000)
}
}, [user])
@@ -106,13 +114,13 @@ Source: screens/auth/RegistrationScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_auth_TermsAndConditionsScreen.js.html b/docs/api/app/screens_auth_TermsAndConditionsScreen.js.html
index 7d6328e6..3e9603e6 100644
--- a/docs/api/app/screens_auth_TermsAndConditionsScreen.js.html
+++ b/docs/api/app/screens_auth_TermsAndConditionsScreen.js.html
@@ -44,6 +44,7 @@ Source: screens/auth/TermsAndConditionsScreen.js
const initialState = {
termsAndConditionsIsChecked: false,
+ researchIsChecked: false,
highlightCheckbox: false,
modalOpen: false
}
@@ -89,9 +90,14 @@ Source: screens/auth/TermsAndConditionsScreen.js
InteractionGraph.action({
type: 'select', target: checkboxHandler.name, details: { type, value: currentValue }
})
+
const options = { type, [type]: !currentValue }
dispatch(options)
+ if (type === 'research') {
+ dispatch({ type: 'research', research: true })
+ }
+
if (type === 'terms' && !currentValue) {
dispatch({ type: 'highlight', highlight: false })
}
@@ -290,13 +296,13 @@ Source: screens/auth/TermsAndConditionsScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_auth_WelcomeScreen.js.html b/docs/api/app/screens_auth_WelcomeScreen.js.html
index cae01bd2..35a4f625 100644
--- a/docs/api/app/screens_auth_WelcomeScreen.js.html
+++ b/docs/api/app/screens_auth_WelcomeScreen.js.html
@@ -136,13 +136,13 @@ Source: screens/auth/WelcomeScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_complete_CompleteScreen.js.html b/docs/api/app/screens_complete_CompleteScreen.js.html
index 5ad19e49..6bf0d434 100644
--- a/docs/api/app/screens_complete_CompleteScreen.js.html
+++ b/docs/api/app/screens_complete_CompleteScreen.js.html
@@ -34,7 +34,7 @@ Source: screens/complete/CompleteScreen.js
import { useTranslation } from 'react-i18next'
import { useDocs } from '../../meteor/useDocs'
import { loadCompleteData } from './loadCompleteData'
-import { useTts } from '../../components/Tts'
+import { TTSengine, useTts } from '../../components/Tts'
import { getDimensionColor } from '../unit/getDimensionColor'
import { ActionButton } from '../../components/ActionButton'
import { Celebrate } from './Celebrate'
@@ -51,7 +51,7 @@ Source: screens/complete/CompleteScreen.js
const COMPLETE = 'complete'
-Sound.load(COMPLETE, () => require('../../assets/audio/trophy_animation.mp3'))
+Sound.load(COMPLETE, () => require('../../../assets/audio/trophy_animation.mp3'))
/**
* This screen is shown, when no Units are in the queue anymore and the
@@ -80,6 +80,7 @@ Source: screens/complete/CompleteScreen.js
// ---------------------------------------------------------------------------
useEffect(() => {
Vibration.vibrate(1000)
+ TTSengine.stop()
Sound.play(COMPLETE).catch(Log.error)
return () => Sound.unload()
@@ -258,13 +259,13 @@ Source: screens/complete/CompleteScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_home_HomeScreen.js.html b/docs/api/app/screens_home_HomeScreen.js.html
index 3618a99d..282fc4f8 100644
--- a/docs/api/app/screens_home_HomeScreen.js.html
+++ b/docs/api/app/screens_home_HomeScreen.js.html
@@ -29,6 +29,7 @@ Source: screens/home/HomeScreen.js
import React, { useCallback, useContext } from 'react'
import { useTts } from '../../components/Tts'
import { useTranslation } from 'react-i18next'
+import { useRefresh } from '../../hooks/useRefresh'
import { useDocs } from '../../meteor/useDocs'
import { loadHomeData } from './loadHomeData'
import { createStyleSheet } from '../../styles/createStyleSheet'
@@ -59,10 +60,16 @@ Source: screens/home/HomeScreen.js
const { Tts } = useTts()
const [/* session */, sessionActions] = useContext(AppSessionContext)
const { syncRequired, complete, progress } = useSync()
- const { data, error, loading } = useDocs({
+ const [reload, refresh] = useRefresh()
+ const { data, error, loading, loadMessage } = useDocs({
fn: () => loadHomeData({ syncRequired, complete }),
- runArgs: [syncRequired, complete]
+ runArgs: [syncRequired, complete],
+ maxAttempts: 1,
+ dataRequired: true,
+ message: 'homeScreen.loading',
+ reload
})
+
const selectField = useCallback(async value => {
const { _id, title } = value
MapIcons.setField(_id)
@@ -101,7 +108,14 @@ Source: screens/home/HomeScreen.js
}
return (
- <ScreenBase data={data} loading={loading} error={error} style={styles.container}>
+ <ScreenBase
+ data={data}
+ loading={loading}
+ error={error}
+ style={styles.container}
+ loadMessage={loadMessage}
+ onRefresh={refresh}
+ >
<ScrollView contentContainerStyle={styles.scrollContainer}>
<View style={styles.textContainer}>
<Tts
@@ -159,13 +173,13 @@ Source: screens/home/HomeScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_home_loadHomeData.js.html b/docs/api/app/screens_home_loadHomeData.js.html
new file mode 100644
index 00000000..f3cbfd72
--- /dev/null
+++ b/docs/api/app/screens_home_loadHomeData.js.html
@@ -0,0 +1,75 @@
+
+
+
+
+ JSDoc: Source: screens/home/loadHomeData.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: screens/home/loadHomeData.js
+
+
+
+
+
+
+
+
+ import { Field } from '../../contexts/Field'
+import { Order } from '../../contexts/Order'
+import { byOrderedIds } from '../../utils/array/byOrderedIds'
+import { Log } from '../../infrastructure/Log'
+
+/**
+ * Loads the available fields for the home screen.
+ * @return {Promise<object[]>}
+ */
+export const loadHomeData = async () => {
+ const fields = Field.collection().find().fetch()
+ const order = Order.collection().findOne()
+
+ if (Array.isArray(order?.fields) && order.fields.length > 0) {
+ debug('sort fields by order:')
+ Log.print({ fields })
+ Log.print({ 'order.fields': order.fields })
+ fields.sort(byOrderedIds(order.fields))
+ }
+
+ return fields
+}
+
+const debug = Log.create(loadHomeData.name, 'debug')
+
+
+
+
+
+
+
+
+
+
+ Home Classes Global
+
+
+
+
+
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
+
+
+
+
+
+
diff --git a/docs/api/app/screens_map_DimensionScreen.js.html b/docs/api/app/screens_map_DimensionScreen.js.html
index c715c801..4af11a00 100644
--- a/docs/api/app/screens_map_DimensionScreen.js.html
+++ b/docs/api/app/screens_map_DimensionScreen.js.html
@@ -209,13 +209,13 @@ Source: screens/map/DimensionScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_map_MapScreen.js.html b/docs/api/app/screens_map_MapScreen.js.html
index 6b4054c0..09960508 100644
--- a/docs/api/app/screens_map_MapScreen.js.html
+++ b/docs/api/app/screens_map_MapScreen.js.html
@@ -31,6 +31,7 @@ Source: screens/map/MapScreen.js
import { createStyleSheet } from '../../styles/createStyleSheet'
import { useDocs } from '../../meteor/useDocs'
import { loadMapData } from './loadMapData'
+import { useRefresh } from '../../hooks/useRefresh'
import { Log } from '../../infrastructure/Log'
import { useTranslation } from 'react-i18next'
import { AppSessionContext } from '../../state/AppSessionContext'
@@ -45,9 +46,10 @@ Source: screens/map/MapScreen.js
import { Connector } from './components/Connector'
import nextFrame from 'next-frame'
import { MapIcons } from '../../contexts/MapIcons'
+import { Layout } from '../../constants/Layout'
const log = Log.create('MapScreen')
-const ITEM_HEIGHT = 100
+const STAGE_SIZE = 100
const counter = 0.75
/**
@@ -68,6 +70,9 @@ Source: screens/map/MapScreen.js
const { Tts } = useTts()
const [stageConnectorWidth, setStageConnectorWidth] = useState(null)
const [activeStage, setActiveStage] = useState(-1)
+ const [initialIndex, setInitialIndex] = useState(0)
+ const [listData, setListData] = useState([])
+ const [reload, refresh] = useRefresh()
const [connectorWidth, setConnectorWidth] = useState(null)
const [session, sessionActions] = useContext(AppSessionContext)
const mapDocs = useDocs({
@@ -79,7 +84,10 @@ Source: screens/map/MapScreen.js
onUserDataLoaded: () => {
sessionActions.update({ loadUserData: null })
}
- })
+ }),
+ dataRequired: true,
+ reload,
+ message: 'mapScreen.loadData'
})
useEffect(() => {
@@ -96,11 +104,26 @@ Source: screens/map/MapScreen.js
})
}, [props.navigation, sessionActions])
+ useEffect(() => {
+ const progressIndex = mapDocs?.data?.progressIndex
+ const entries = mapDocs?.data?.entries
+
+ if (progressIndex && progressIndex !== initialIndex) {
+ setInitialIndex(progressIndex)
+ }
+
+ if (entries && listData.length === 0) {
+ setListData(entries)
+ }
+ }, [mapDocs])
+
const onListLayoutDetected = useCallback((event) => {
const { width } = event.nativeEvent.layout
- setStageConnectorWidth(width - ITEM_HEIGHT - (ITEM_HEIGHT / 2))
- setConnectorWidth((width / 2) - ITEM_HEIGHT)
- }, [setStageConnectorWidth, setConnectorWidth])
+ if (!stageConnectorWidth) {
+ setStageConnectorWidth(width - STAGE_SIZE - (STAGE_SIZE / 2))
+ setConnectorWidth((width / 2) - STAGE_SIZE)
+ }
+ }, [])
const selectStage = useCallback(async (stage, index) => {
setActiveStage(index)
@@ -115,10 +138,10 @@ Source: screens/map/MapScreen.js
props.navigation.navigate('dimension')
}, [mapDocs])
- const renderListItem = useCallback(({ index, item: entry }) => {
+ const renderListItem2 = useCallback(({ index, item: entry }) => {
if (entry.type === 'stage') {
const isActive = activeStage === index
- return renderStage({
+ const stageData = {
index,
stage: entry,
connectorWidth: stageConnectorWidth,
@@ -126,19 +149,20 @@ Source: screens/map/MapScreen.js
isActive,
dimensionOrder: mapData?.dimensionOrder,
dimensions: mapData?.dimensions
- })
+ }
+ return (<MapStage {...stageData} />)
}
if (entry.type === 'milestone') {
- return renderMilestone({ milestone: entry, connectorWidth })
+ return (<MapMilestone milestone={entry} connectorWidth={connectorWidth} />)
}
if (entry.type === 'finish') {
return (
<View style={styles.stage}>
- {renderConnector(entry.viewPosition.left, connectorWidth)}
+ <MapConnector connectorId={entry.viewPosition.left} listWidth={connectorWidth} />
<MapFinish />
- {renderConnector(entry.viewPosition.right, connectorWidth)}
+ <MapConnector connectorId={entry.viewPosition.right} listWidth={connectorWidth} />
</View>
)
}
@@ -146,9 +170,9 @@ Source: screens/map/MapScreen.js
if (entry.type === 'start') {
return (
<View style={styles.stage}>
- {renderConnector(entry.viewPosition.left, connectorWidth)}
+ <MapConnector connectorId={entry.viewPosition.left} listWidth={connectorWidth} />
{MapIcons.render(0)}
- {renderConnector(entry.viewPosition.right, connectorWidth)}
+ <MapConnector connectorId={entry.viewPosition.right} listWidth={connectorWidth} />
</View>
)
}
@@ -186,58 +210,52 @@ Source: screens/map/MapScreen.js
* }
*/
const mapData = mapDocs.data
-
const renderList = () => {
- if (!mapData?.entries?.length) {
+ if (!listData || !stageConnectorWidth) {
return null
}
-
- // return mapData.entries.map((item, index) => renderListItem({ index, item }))
-
return (
- <View style={styles.scrollView}>
- <FlatList
- data={mapData.entries}
- renderItem={renderListItem}
- onLayout={onListLayoutDetected}
- inverted
- decelerationRate='fast'
- disableIntervalMomentum
- initialScrollIndex={mapData.progressIndex ?? 0}
- removeClippedSubviews
- persistentScrollbar
- keyExtractor={flatListKeyExtractor}
- initialNumToRender={50}
- maxToRenderPerBatch={50}
- updateCellsBatchingPeriod={3000}
- getItemLayout={flatListGetItemLayout}
- />
- </View>
+ <FlatList
+ data={listData}
+ renderItem={renderListItem2}
+ inverted
+ decelerationRate='fast'
+ disableIntervalMomentum
+ initialScrollIndex={initialIndex}
+ removeClippedSubviews
+ persistentScrollbar
+ keyExtractor={flatListKeyExtractor}
+ initialNumToRender={10}
+ maxToRenderPerBatch={3}
+ updateCellsBatchingPeriod={50}
+ getItemLayout={flatListGetItemLayout}
+ />
)
}
-
return (
<ScreenBase
{...mapDocs}
- loadMessage={t('mapScreen.loadData')}
progress={counter}
- style={styles.container}
+ onRefresh={refresh}
+ style={mapDocs.error ? styles.failedContainer : styles.container}
>
- {renderList()}
+ <View style={styles.scrollView} onLayout={onListLayoutDetected}>
+ {renderList()}
+ </View>
</ScreenBase>
)
}
const flatListGetItemLayout = (data, index) => {
- const entry = data[index]
- const length = entry && ['stage', 'milestone'].includes(entry.type)
- ? ITEM_HEIGHT + 10
- : 59
- return { length, offset: length * index, index }
+ // const entry = data[index]
+ // const length = entry && ['stage', 'milestone'].includes(entry.type)
+ // ? ITEM_HEIGHT + 10
+ // : 59
+ return { length: STAGE_SIZE, offset: STAGE_SIZE * index, index }
}
const flatListKeyExtractor = (item) => item.entryKey
-const renderStage = ({ index, stage, selectStage, connectorWidth, dimensions, dimensionOrder, isActive }) => {
+const MapStage = React.memo(({ index, stage, selectStage, connectorWidth, dimensions, dimensionOrder, isActive }) => {
const progress = 100 * (stage.userProgress || 0) / stage.progress
const justifyContent = positionMap[stage.viewPosition.current]
const stageStyle = mergeStyles(styles.stage, { justifyContent })
@@ -245,10 +263,10 @@ Source: screens/map/MapScreen.js
return (
<View style={stageStyle}>
- {renderConnector(viewPosition.left, connectorWidth, viewPosition.icon)}
+ <MapConnector connectorId={viewPosition.left} listWidth={connectorWidth} withIcon={viewPosition.icon} />
<Stage
- width={ITEM_HEIGHT}
- height={ITEM_HEIGHT}
+ width={STAGE_SIZE}
+ height={STAGE_SIZE}
onPress={() => selectStage(stage, index)}
unitSets={stage.unitSets}
dimensions={dimensions}
@@ -257,23 +275,23 @@ Source: screens/map/MapScreen.js
progress={progress}
isActive={isActive}
/>
- {renderConnector(viewPosition.right, connectorWidth, viewPosition.icon)}
+ <MapConnector connectorId={viewPosition.right} listWidth={connectorWidth} withIcon={viewPosition.icon} />
</View>
)
-}
+})
-const renderMilestone = ({ milestone, connectorWidth }) => {
+const MapMilestone = React.memo(({ milestone, connectorWidth }) => {
const progress = 100 * milestone.userProgress / milestone.maxProgress
return (
<View style={styles.stage}>
- {renderConnector(milestone.viewPosition.left, connectorWidth)}
+ <MapConnector connectorId={milestone.viewPosition.left} listWidth={connectorWidth} />
<Milestone progress={progress} level={milestone.level + 1} />
- {renderConnector(milestone.viewPosition.right, connectorWidth)}
+ <MapConnector connectorId={milestone.viewPosition.right} listWidth={connectorWidth} />
</View>
)
-}
+})
-const renderConnector = (connectorId, listWidth, withIcon = -1) => {
+const MapConnector = React.memo(({ connectorId, listWidth, withIcon = -1 }) => {
if (connectorId === 'fill') {
return (
<LeaText style={{ width: listWidth ?? '100%' }} />
@@ -286,7 +304,8 @@ Source: screens/map/MapScreen.js
}
return null
-}
+})
+
const positionMap = {
center: 'center',
left: 'flex-start',
@@ -302,6 +321,9 @@ Source: screens/map/MapScreen.js
marginLeft: 15,
marginRight: 15
},
+ failedContainer: {
+ ...Layout.container()
+ },
scrollView: {
width: '100%'
},
@@ -310,7 +332,8 @@ Source: screens/map/MapScreen.js
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
- height: ITEM_HEIGHT
+ height: STAGE_SIZE,
+ borderColor: 'blue'
},
connector: {
flexGrow: 1
@@ -332,13 +355,13 @@ Source: screens/map/MapScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_map_components_Connector.js.html b/docs/api/app/screens_map_components_Connector.js.html
index 69a2e8a0..9e4a52b0 100644
--- a/docs/api/app/screens_map_components_Connector.js.html
+++ b/docs/api/app/screens_map_components_Connector.js.html
@@ -26,10 +26,11 @@ Source: screens/map/components/Connector.js
- import React from 'react'
+ import React, { useEffect, useState } from 'react'
import Svg, { G, Path } from 'react-native-svg'
import { Colors } from '../../../constants/Colors'
import { MapIcons } from '../../../contexts/MapIcons'
+import { memoize } from '../../../utils/memoize'
/**
* A connector renders an SVG-based, L-shaped line from one
@@ -47,9 +48,64 @@ Source: screens/map/components/Connector.js
* @component
*/
const ConnectorComponent = props => {
- // TODO put in effect + state
- const { from, width = 100, height = 100 } = props
- const [/* to */, direction = 'up'] = props.to.split('-')
+ const { from, to, width = 100, height = 100 } = props
+ const path = usePath(from, to, width, height)
+
+ return (
+ <Svg xmlns='http://www.w3.org/2000/svg' width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
+ <G id='Ebene_1-2' data-name='Ebene 1'>
+ <Path
+ className='cls-1'
+ strokeWidth='2'
+ strokeMiterlimit='10'
+ stroke={Colors.gray}
+ fill={Colors.transparent}
+ d={path}
+ />
+ <ConnectorIcon width={width} from={from} height={height} icon={props.icon} />
+ </G>
+ </Svg>
+ )
+}
+
+const ConnectorIcon = React.memo(props => {
+ if (typeof props.icon !== 'number') {
+ return null
+ }
+ const { width, from, height, icon } = props
+ const fromLeft = from === 'left'
+ const part = width / 7
+ const offset = fromLeft
+ ? part
+ : part * -1
+ const xPos = width / 2 + offset
+ return (
+ <G x={xPos} y={height / 4} width={50} height={50}>
+ {MapIcons.render(icon)}
+ </G>
+ )
+})
+
+/**
+ *
+ * @param from
+ * @param to
+ * @param width
+ * @param height
+ * @return {string}
+ */
+const usePath = (from, to, width, height) => {
+ const [path, setPath] = useState(null)
+ useEffect(() => {
+ const p = pathMemo(from, to, width, height)
+ setPath(p)
+ }, [from, to, width, height])
+ return path
+}
+
+const [pathMemo] = memoize((...args) => {
+ const [from, to, width, height] = args
+ const [/* to */, direction = 'up'] = to.split('-')
const halfHeight = Math.round(height / 2)
const fromLeft = from === 'left'
const startX = fromLeft
@@ -71,39 +127,8 @@ Source: screens/map/components/Connector.js
`L ${endX} ${endY}`
]
- const renderIcon = () => {
- if (typeof props.icon !== 'number') {
- return null
- }
-
- const part = width / 7
- const offset = fromLeft
- ? part
- : part * -1
- const xPos = width / 2 + offset
- return (
- <G x={xPos} y={height / 4} width={50} height={50}>
- {MapIcons.render(props.icon)}
- </G>
- )
- }
-
- return (
- <Svg xmlns='http://www.w3.org/2000/svg' width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
- <G id='Ebene_1-2' data-name='Ebene 1'>
- <Path
- className='cls-1'
- strokeWidth='2'
- strokeMiterlimit='10'
- stroke={Colors.gray}
- fill={Colors.transparent}
- d={execution.join(' ')}
- />
- {renderIcon()}
- </G>
- </Svg>
- )
-}
+ return execution.join(' ')
+})
export const Connector = React.memo(ConnectorComponent)
@@ -116,13 +141,13 @@ Source: screens/map/components/Connector.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_map_components_Milestone.js.html b/docs/api/app/screens_map_components_Milestone.js.html
index 248d14dc..3642b56e 100644
--- a/docs/api/app/screens_map_components_Milestone.js.html
+++ b/docs/api/app/screens_map_components_Milestone.js.html
@@ -82,20 +82,23 @@ Source: screens/map/components/Milestone.js
const xOffsetLeft = ((value - 1) * 9) / 2
for (let i = 0; i < value; i++) {
- stars[i] = renderStar(i, xOffsetLeft)
+ stars[i] = (<MilestoneStar key={i} index={i} xOffsetLeft={xOffsetLeft} />)
}
return stars
}
-const renderStar = (index, xOffsetLeft) => (
- <Path
- key={`milestone-star-${index}`}
- translateX={(index * 9) - xOffsetLeft}
- id='Pfad_123-2' data-name='Pfad 123-2' fill='white'
- d='M25.37,32.76l-1,2-2.25.32a.5.5,0,0,0-.42.56.48.48,0,0,0,.15.28l1.62,1.59-.38,2.24a.49.49,0,0,0,.4.57.54.54,0,0,0,.31,0l2-1.06,2,1.06a.5.5,0,0,0,.66-.21.49.49,0,0,0,.05-.31l-.39-2.24L29.78,36a.48.48,0,0,0,0-.69.51.51,0,0,0-.28-.15l-2.25-.32-1-2a.48.48,0,0,0-.66-.22.43.43,0,0,0-.22.22Z'
- />
-)
+const MilestoneStar = React.memo((props) => {
+ const { index, xOffsetLeft } = props
+ return (
+ <Path
+ key={`milestone-star-${index}`}
+ translateX={(index * 9) - xOffsetLeft}
+ id='Pfad_123-2' data-name='Pfad 123-2' fill='white'
+ d='M25.37,32.76l-1,2-2.25.32a.5.5,0,0,0-.42.56.48.48,0,0,0,.15.28l1.62,1.59-.38,2.24a.49.49,0,0,0,.4.57.54.54,0,0,0,.31,0l2-1.06,2,1.06a.5.5,0,0,0,.66-.21.49.49,0,0,0,.05-.31l-.39-2.24L29.78,36a.48.48,0,0,0,0-.69.51.51,0,0,0-.28-.15l-2.25-.32-1-2a.48.48,0,0,0-.66-.22.43.43,0,0,0-.22.22Z'
+ />
+ )
+})
export const Milestone = React.memo(MilestoneComponent)
@@ -108,13 +111,13 @@ Source: screens/map/components/Milestone.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_map_components_Stage.js.html b/docs/api/app/screens_map_components_Stage.js.html
index 90a7e7d7..95bf5670 100644
--- a/docs/api/app/screens_map_components_Stage.js.html
+++ b/docs/api/app/screens_map_components_Stage.js.html
@@ -218,13 +218,13 @@ Source: screens/map/components/Stage.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_map_loadMapData.js.html b/docs/api/app/screens_map_loadMapData.js.html
index d759bb15..4853780f 100644
--- a/docs/api/app/screens_map_loadMapData.js.html
+++ b/docs/api/app/screens_map_loadMapData.js.html
@@ -68,12 +68,18 @@ Source: screens/map/loadMapData.js
// 1. for the current field get map data from cache
// or load from server, field is required at this step
- const mapData = mapCache.has(fieldId)
- ? mapCache.get(fieldId)
- : await callMeteor({
+ let mapData
+
+ if (mapCache.has(fieldId)) {
+ mapData = mapCache.get(fieldId)
+ }
+
+ if (!mapData) {
+ mapData = await callMeteor({
name: Config.methods.getMapData,
args: { fieldId }
})
+ }
// 2. if data is incomplete return null
// this requires dimensions, levels and entries
@@ -88,7 +94,7 @@ Source: screens/map/loadMapData.js
debug('data incomplete, skip with null')
debug({ hasData, hasDimensions, hasEntries, hasLevels })
debug({ mapData })
- return null
+ return { empty: true }
}
await nextFrame()
@@ -359,13 +365,13 @@ Source: screens/map/loadMapData.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_profile_ProfileScreen.js.html b/docs/api/app/screens_profile_ProfileScreen.js.html
index b32bbc29..fea2a386 100644
--- a/docs/api/app/screens_profile_ProfileScreen.js.html
+++ b/docs/api/app/screens_profile_ProfileScreen.js.html
@@ -31,7 +31,6 @@ Source: screens/profile/ProfileScreen.js
import { AccountInfo } from './account/AccountInfo'
import { createStyleSheet } from '../../styles/createStyleSheet'
import { Layout } from '../../constants/Layout'
-import { TTSSettings } from './TTSSettings'
import { useTimeout } from '../../hooks/useTimeout'
import { Loading } from '../../components/Loading'
import { Colors } from '../../constants/Colors'
@@ -58,6 +57,7 @@ Source: screens/profile/ProfileScreen.js
return (
<SafeAreaView style={styles.container}>
<ActionButton
+ containerStyle={{ marginTop: 25 }}
buttonStyle={styles.achievementsButton}
titleStyle={styles.achievementButtonTitle}
iconColor={Colors.secondary}
@@ -65,16 +65,15 @@ Source: screens/profile/ProfileScreen.js
onPress={() => props.navigation.navigate('achievements')}
title={t('profileScreen.achievements.title')}
/>
- <View style={styles.headline}>
- <Tts
- text={t('tts.settings')}
- color={Colors.secondary}
- align='center'
- fontStyle={styles.headlineText}
- id='profileScreen.tts.settings'
- />
- </View>
- <TTSSettings containerStyle={styles.tts} />
+ <ActionButton
+ buttonStyle={styles.achievementsButton}
+ containerStyle={{ marginTop: 25 }}
+ titleStyle={styles.achievementButtonTitle}
+ iconColor={Colors.secondary}
+ color={Colors.white}
+ onPress={() => props.navigation.navigate('ttsprofile')}
+ title={t('tts.settings')}
+ />
<View style={styles.headline}>
<Tts
text={t('accountInfo.title')}
@@ -134,13 +133,13 @@ Source: screens/profile/ProfileScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_profile_account_AccountInfo.js.html b/docs/api/app/screens_profile_account_AccountInfo.js.html
index 84b2391f..521f061d 100644
--- a/docs/api/app/screens_profile_account_AccountInfo.js.html
+++ b/docs/api/app/screens_profile_account_AccountInfo.js.html
@@ -42,7 +42,7 @@ Source: screens/profile/account/AccountInfo.js
import { useDocs } from '../../../meteor/useDocs'
import { loadAccountData } from './loadAccountData'
import { Markdown } from '../../../components/MarkdownWithTTS'
-import { Icon } from 'react-native-elements'
+import Icon from '@expo/vector-icons/FontAwesome6'
import { AppTerminate } from '../../../infrastructure/app/AppTerminate'
import { clearContextStorage } from '../../../contexts/createContextStorage'
import { Log } from '../../../infrastructure/Log'
@@ -78,7 +78,7 @@ Source: screens/profile/account/AccountInfo.js
},
body: () => (
<>
- <Icon name='check' color={Colors.success} type='font-awesome-5' />
+ <Icon name='check' color={Colors.success} />
<Tts block text={t('accountInfo.close.next')} />
</>
),
@@ -129,7 +129,7 @@ Source: screens/profile/account/AccountInfo.js
instructions: () => t('accountInfo.restore.instructions'),
body: () => (<RequestRestoreCodes onError={onError} />),
deny: {
- icon: 'times',
+ icon: 'xmark',
label: () => t('actions.close'),
handler: () => {
InteractionGraph.action({
@@ -148,7 +148,7 @@ Source: screens/profile/account/AccountInfo.js
*/
actions.signOut = {
key: 'signOut',
- icon: 'sign-out-alt',
+ icon: 'right-from-bracket',
label: () => t('accountInfo.signOut.title'),
onPress: () => setModalContent(actions.signOut.modal),
modal: {
@@ -172,7 +172,7 @@ Source: screens/profile/account/AccountInfo.js
}
},
deny: {
- icon: 'times',
+ icon: 'xmark',
label: () => t('actions.cancel'),
handler: () => setModalContent(null)
}
@@ -186,7 +186,7 @@ Source: screens/profile/account/AccountInfo.js
modal: {
body: () => (
<View style={styles.danger}>
- <Tts block text={t('accountInfo.deleteAccount.instructions')} color={Colors.danger} />
+ <Tts block text={t('accountInfo.deleteAccount.instructions')} color={Colors.secondary} />
</View>
),
approve: {
@@ -195,26 +195,44 @@ Source: screens/profile/account/AccountInfo.js
color: Colors.danger,
titleStyle: styles.dangerText,
handler: () => {
- const onSuccess = () => {
+ const onSuccess = async () => {
lastAction.current = 'deleted'
Sync.reset()
- clearContextStorage(onError)
- .catch(Log.error)
- .then(() => setModalContent(closeModal))
+ try {
+ await clearContextStorage()
+ }
+ catch (error) {
+ onError(error)
+ }
+ setModalContent(actions.deleteAccount.deleted)
}
deleteAccount({ onError, onSuccess })
}
},
deny: {
- icon: 'times',
+ icon: 'xmark',
label: () => t('actions.cancel'),
handler: () => setModalContent(null)
}
+ },
+ deleted: {
+ body: () => (
+ <View>
+ <Tts block text={t('accountInfo.deleteAccount.successful')} color={Colors.secondary} />
+ </View>
+ ),
+ deny: {
+ icon: 'home',
+ label: () => t('actions.toHome'),
+ handler: () => {
+ setModalContent(null)
+ }
+ }
}
}
actions.privacy = {
- icon: 'shield-alt',
+ icon: 'file-shield',
key: 'privacy',
label: () => t('legal.privacy'),
onPress: () => setModalContent(actions.privacy.modal),
@@ -226,7 +244,7 @@ Source: screens/profile/account/AccountInfo.js
)
},
deny: {
- icon: 'times',
+ icon: 'xmark',
label: () => t('actions.close'),
handler: () => setModalContent(null)
}
@@ -246,7 +264,7 @@ Source: screens/profile/account/AccountInfo.js
)
},
deny: {
- icon: 'times',
+ icon: 'xmark',
label: () => t('actions.close'),
handler: () => setModalContent(null)
}
@@ -266,7 +284,7 @@ Source: screens/profile/account/AccountInfo.js
)
},
deny: {
- icon: 'times',
+ icon: 'xmark',
label: () => t('actions.close'),
handler: () => setModalContent(null)
}
@@ -405,13 +423,13 @@ Source: screens/profile/account/AccountInfo.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_profile_achievements_AchievementsScreen.js.html b/docs/api/app/screens_profile_achievements_AchievementsScreen.js.html
index 7aeff1b7..3576d598 100644
--- a/docs/api/app/screens_profile_achievements_AchievementsScreen.js.html
+++ b/docs/api/app/screens_profile_achievements_AchievementsScreen.js.html
@@ -159,13 +159,13 @@ Source: screens/profile/achievements/AchievementsScreen.j
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_profile_achievements_loadAchievementsData.js.html b/docs/api/app/screens_profile_achievements_loadAchievementsData.js.html
index 7baeb3f8..e02df823 100644
--- a/docs/api/app/screens_profile_achievements_loadAchievementsData.js.html
+++ b/docs/api/app/screens_profile_achievements_loadAchievementsData.js.html
@@ -150,13 +150,13 @@ Source: screens/profile/achievements/loadAchievementsData
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_sync_SyncScreen.js.html b/docs/api/app/screens_sync_SyncScreen.js.html
index 006697c3..140e8645 100644
--- a/docs/api/app/screens_sync_SyncScreen.js.html
+++ b/docs/api/app/screens_sync_SyncScreen.js.html
@@ -79,13 +79,13 @@ Source: screens/sync/SyncScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_UnitScreen.js.html b/docs/api/app/screens_unit_UnitScreen.js.html
index 55e4601a..9b88c865 100644
--- a/docs/api/app/screens_unit_UnitScreen.js.html
+++ b/docs/api/app/screens_unit_UnitScreen.js.html
@@ -141,12 +141,12 @@ Source: screens/unit/UnitScreen.js
pressable
question={t('unitScreen.abort.question')}
approveText={t('unitScreen.abort.abort')}
- approveIcon='times'
+ approveIcon='xmark'
denyText={t('unitScreen.abort.continue')}
- denyIcon='edit'
+ denyIcon='marker'
onApprove={() => cancelUnit()}
onDeny={() => {}}
- icon='times'
+ icon='xmark'
tts={false}
style={styles.confirm}
/>
@@ -371,7 +371,7 @@ Source: screens/unit/UnitScreen.js
align='center'
tts={t('unitScreen.actions.check')}
color={dimensionColor}
- icon='edit'
+ icon='marker'
onPress={checkScore}
/>
)
@@ -449,13 +449,13 @@ Source: screens/unit/UnitScreen.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_completeUnit.js.html b/docs/api/app/screens_unit_completeUnit.js.html
index ea54b852..aceebd4b 100644
--- a/docs/api/app/screens_unit_completeUnit.js.html
+++ b/docs/api/app/screens_unit_completeUnit.js.html
@@ -60,13 +60,13 @@ Source: screens/unit/completeUnit.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_createResponseDoc.js.html b/docs/api/app/screens_unit_createResponseDoc.js.html
index d51421aa..9890d331 100644
--- a/docs/api/app/screens_unit_createResponseDoc.js.html
+++ b/docs/api/app/screens_unit_createResponseDoc.js.html
@@ -66,13 +66,13 @@ Source: screens/unit/createResponseDoc.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_getDimensionColor.js.html b/docs/api/app/screens_unit_getDimensionColor.js.html
index 6f4312b2..8653090a 100644
--- a/docs/api/app/screens_unit_getDimensionColor.js.html
+++ b/docs/api/app/screens_unit_getDimensionColor.js.html
@@ -63,13 +63,13 @@ Source: screens/unit/getDimensionColor.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_instructions_ChoiceImageInstructions.js.html b/docs/api/app/screens_unit_instructions_ChoiceImageInstructions.js.html
new file mode 100644
index 00000000..d1de2314
--- /dev/null
+++ b/docs/api/app/screens_unit_instructions_ChoiceImageInstructions.js.html
@@ -0,0 +1,206 @@
+
+
+
+
+ JSDoc: Source: screens/unit/instructions/ChoiceImageInstructions.js
+
+
+
+
+
+
+
+
+
+
+
+
+
Source: screens/unit/instructions/ChoiceImageInstructions.js
+
+
+
+
+
+
+
+
+ import React, { useCallback, useRef, useState } from 'react'
+import { Animated, PixelRatio, Pressable } from 'react-native'
+import { Svg, G, Path } from 'react-native-svg'
+import { createStyleSheet } from '../../../styles/createStyleSheet'
+
+const defaultPosition = { x: 0, y: 0 }
+
+/**
+ *
+ * @param props
+ * @return {Element}
+ * @constructor
+ */
+export const ChoiceImageInstructions = props => {
+ const handPosition = useRef(new Animated.ValueXY(defaultPosition)).current
+ const handAnimation = useRef({ animation: null, running: false })
+ const [selected, setSelected] = useState(false)
+ const [size, setSize] = useState(0)
+
+ const onContainerLayout = event => {
+ const { width } = event.nativeEvent.layout
+ setSize(PixelRatio.roundToNearestPixel(width / 5))
+ }
+
+ const runAnimation = useCallback(() => {
+ if (handAnimation.current.running === false) {
+ return
+ }
+ const anim = handAnimation.current.animation ?? Animated.timing(handPosition, {
+ toValue: { x: props.width / 2 - 30, y: 0 },
+ duration: 1000,
+ useNativeDriver: false
+ })
+
+ if (handAnimation.current.animation === null) {
+ handAnimation.current.animation = anim
+ }
+
+ anim.start(() => {
+ setSelected(true)
+ anim.reset()
+ setTimeout(() => {
+ endAnimation()
+ }, 750)
+ })
+ }, [])
+
+ const endAnimation = () => {
+ handAnimation.current.running = false
+ handAnimation.current.animation.stop()
+ handAnimation.current.animation.reset()
+ setSelected(false)
+ }
+
+ const toggleAnimation = () => {
+ if (handAnimation.current.running) {
+ endAnimation()
+ }
+ else {
+ handAnimation.current.running = true
+ runAnimation()
+ }
+ if (props.onPress) {
+ props.onPress({ running: handAnimation.current.running })
+ }
+ }
+
+ return (
+ <Pressable
+ onLayout={onContainerLayout}
+ accessibilityRole='button'
+ onPress={toggleAnimation}
+ style={styles.container}
+ >
+ <Animated.View
+ style={[
+ handPosition.getLayout(),
+ styles.svgContainer
+ ]}
+ direction='alternate'
+ easing='linear'
+ iterationCount='infinite'
+ useNativeDriver
+ >
+ <HandMove width={size} height={size} />
+ </Animated.View>
+ <Animated.View
+ style={[
+ {
+ left: props.width / 2 - 30,
+ top: 0
+ },
+ styles.svgContainer
+ ]}
+ direction='alternate'
+ easing='linear'
+ iterationCount='infinite'
+ useNativeDriver
+ >
+ <ColorListImg width={size} height={size} selected={selected} />
+ </Animated.View>
+ </Pressable>
+ )
+}
+
+const ColorListImg = props => {
+ return (
+ <Svg width={props.width} height={props.height} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 46.58 52.34'>
+ <G id='Ebene_2' data-name='Ebene 2'>
+ <G id='Ebene_1-2' data-name='Ebene 1'>
+ <Path id='Pfad_163' data-name='Pfad 163' fill={props.selected ? '#5bb984' : '#183b5d'} d='M16.18,0H.85C.38,0,0,1.76,0,3.94v7.87c0,2.18.38,3.94.85,3.94H16.18c.47,0,.85-1.76.85-3.94V3.94C17,1.76,16.65,0,16.18,0Z' />
+ <Path id='Pfad_164' data-name='Pfad 164' fill='#183b5d' d='M45.73,0H30.41c-.47,0-.85,1.76-.85,3.94v7.87c0,2.18.38,3.94.85,3.94H45.73c.47,0,.85-1.76.85-3.94V3.94C46.58,1.76,46.2,0,45.73,0Z' />
+ <Path id='Pfad_165' data-name='Pfad 165' fill='#183b5d' d='M16.18,28.33H.85C.38,28.33,0,30.1,0,32.27v7.88c0,2.17.38,3.93.85,3.93H16.18c.47,0,.85-1.76.85-3.93V32.27C17,30.1,16.65,28.33,16.18,28.33Z' />
+ <Path id='Pfad_166' data-name='Pfad 166' fill='#183b5d' d='M45.73,28.33H30.41c-.47,0-.85,1.77-.85,3.94v7.88c0,2.17.38,3.93.85,3.93H45.73c.47,0,.85-1.76.85-3.93V32.27C46.58,30.1,46.2,28.33,45.73,28.33Z' />
+ <Path id='Pfad_167' data-name='Pfad 167' fill='#183b5d' d='M38.09,17.5A3.33,3.33,0,0,0,38,24.16h.12a3.33,3.33,0,0,0,0-6.66Z' />
+ <Path id='Pfad_168' data-name='Pfad 168' fill={props.selected ? '#5bb984' : '#183b5d'} d='M8.24,17.5a3.33,3.33,0,1,0-.11,6.66h.11a3.33,3.33,0,1,0,.12-6.66Z' />
+ <Path id='Pfad_169' data-name='Pfad 169' fill='#183b5d' d='M38.09,45.68A3.33,3.33,0,0,0,38,52.34h.12a3.33,3.33,0,0,0,0-6.66Z' />
+ <Path id='Pfad_170' data-name='Pfad 170' fill='#183b5d' d='M8.24,45.68a3.33,3.33,0,1,0-.11,6.66h.11a3.33,3.33,0,1,0,.12-6.66Z' />
+ </G>
+ </G>
+ </Svg>
+ )
+}
+
+const HandMove = props => {
+ return (
+ <Svg width={props.width} height={props.height} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36.9 32.14'>
+ <G id='Ebene_2' data-name='Ebene 2'>
+ <G id='Ebene_1-2' data-name='Ebene 1'>
+ <G id='Gruppe_282' data-name='Gruppe 282'>
+ <Path
+ id='Pfad_171-4' data-name='Pfad 171-4' fill='#183b5d'
+ d='M32.43,2.28a3.48,3.48,0,0,1,4.31,2,3.42,3.42,0,0,1-2.1,4.26l-7.15,2.51a3.74,3.74,0,0,1,1.42,5.08l-.09.16a3.47,3.47,0,0,1,.39,4.87c1.88,3.29.22,5.65-3.41,6.93l-1.15.39c-4.43,1.57-6.28-.29-9.82.36a1.81,1.81,0,0,1-2-1.19L8.48,15.39h0a3.63,3.63,0,0,1,.93-3.86c1.74-1.65,5.6-5.91,5.75-8.24A3.23,3.23,0,0,1,17.3.21a3.63,3.63,0,0,1,4.64,2.23,3.75,3.75,0,0,1,.2,1.45A11,11,0,0,1,21.75,6L32.43,2.27ZM7,14.77,11.8,28.51a1.83,1.83,0,0,1-1.12,2.32L7.25,32a1.82,1.82,0,0,1-2.32-1.12L.1,17.18a1.83,1.83,0,0,1,1.12-2.32l3.43-1.21A1.82,1.82,0,0,1,7,14.77ZM9.19,27.5a1.52,1.52,0,1,0-.93,1.93h0a1.51,1.51,0,0,0,.93-1.93Z'
+ />
+ </G>
+ </G>
+ </G>
+ </Svg>
+ )
+}
+
+const styles = createStyleSheet({
+ container: {
+ borderColor: '#00f',
+ flexDirection: 'row',
+ flexGrow: 1,
+ alignItems: 'flex-start',
+ justifyContent: 'flex-start'
+ },
+ svgContainer: {
+ flex: 0,
+ justifyContent: 'center',
+ alignSelf: 'center'
+ }
+})
+
+
+
+
+
+
+
+
+
+
+ Home Classes Global
+
+
+
+
+
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
+
+
+
+
+
+
diff --git a/docs/api/app/screens_unit_renderer_ContentRenderer.js.html b/docs/api/app/screens_unit_renderer_ContentRenderer.js.html
index 7b1a028f..dfb6aeee 100644
--- a/docs/api/app/screens_unit_renderer_ContentRenderer.js.html
+++ b/docs/api/app/screens_unit_renderer_ContentRenderer.js.html
@@ -98,13 +98,13 @@ Source: screens/unit/renderer/ContentRenderer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_renderer_InstructionsGraphicsRenderer.js.html b/docs/api/app/screens_unit_renderer_InstructionsGraphicsRenderer.js.html
index 11d93a94..d11a15cf 100644
--- a/docs/api/app/screens_unit_renderer_InstructionsGraphicsRenderer.js.html
+++ b/docs/api/app/screens_unit_renderer_InstructionsGraphicsRenderer.js.html
@@ -26,10 +26,10 @@ Source: screens/unit/renderer/InstructionsGraphicsRendere
- import React, { useEffect, useState } from 'react'
-import { View } from 'react-native'
+ import React, { useCallback, useEffect, useState } from 'react'
+import { Vibration, View } from 'react-native'
import { InstructionAnimations } from '../instructions/InstructionAnimations'
-import { useTts } from '../../../components/Tts'
+import { useTts, TTSengine } from '../../../components/Tts'
import { createStyleSheet } from '../../../styles/createStyleSheet'
import { Loading } from '../../../components/Loading'
@@ -58,6 +58,11 @@ Source: screens/unit/renderer/InstructionsGraphicsRendere
return () => clearTimeout(timer)
}, [text, subtype, page])
+ const onInstructionPress = useCallback(() => {
+ Vibration.vibrate(100)
+ TTSengine.speakImmediately(text)
+ }, [text])
+
if (loading) {
return (
<View style={styles.loadContainer}>
@@ -77,7 +82,9 @@ Source: screens/unit/renderer/InstructionsGraphicsRendere
return (
<View style={styles.container}>
<Tts ttsText={text} color={color} dontShowText />
- <Instruction color={color} height={100} width={200} />
+ <View style={styles.instructionWrapper}>
+ <Instruction color={color} height={100} width={200} onPress={onInstructionPress} />
+ </View>
</View>
)
}
@@ -97,6 +104,12 @@ Source: screens/unit/renderer/InstructionsGraphicsRendere
tts: {
margin: 0,
padding: 0
+ },
+ instructionWrapper: {
+ flex: 1,
+ paddingLeft: 20,
+ paddingTop: 15,
+ paddingBottom: 15
}
})
@@ -109,13 +122,13 @@ Source: screens/unit/renderer/InstructionsGraphicsRendere
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_renderer_UnitRenderer.js.html b/docs/api/app/screens_unit_renderer_UnitRenderer.js.html
index 860ee598..bddc845d 100644
--- a/docs/api/app/screens_unit_renderer_UnitRenderer.js.html
+++ b/docs/api/app/screens_unit_renderer_UnitRenderer.js.html
@@ -27,16 +27,16 @@ Source: screens/unit/renderer/UnitRenderer.js
import React, { useRef, useEffect, useState } from 'react'
-import { ScrollView, Vibration, View } from 'react-native'
+import { KeyboardAvoidingView, ScrollView, Vibration, View } from 'react-native'
import { FadePanel } from '../../../components/FadePanel'
import { mergeStyles } from '../../../styles/mergeStyles'
import { LeaText } from '../../../components/LeaText'
-import { Icon } from 'react-native-elements'
+import Icon from '@expo/vector-icons/FontAwesome6'
import { Colors } from '../../../constants/Colors'
import { createStyleSheet } from '../../../styles/createStyleSheet'
import { InstructionsGraphicsRenderer } from './InstructionsGraphicsRenderer'
import { useTranslation } from 'react-i18next'
-import { useTts } from '../../../components/Tts'
+import { TTSengine, useTts } from '../../../components/Tts'
import { Layout } from '../../../constants/Layout'
import { useKeyboardVisibilityHandler } from '../../../hooks/useKeyboardVisibilityHandler'
import { Sound } from '../../../env/Sound'
@@ -44,9 +44,10 @@ Source: screens/unit/renderer/UnitRenderer.js
import { ContentRenderer } from './ContentRenderer'
import { useItemSubType } from '../useItemSubType'
import { Log } from '../../../infrastructure/Log'
+import { isIOS } from '../../../utils/isIOS'
const PureContentRenderer = React.memo(ContentRenderer)
-
+const debug = Log.create('UnitRenderer', 'debug')
/**
* Renders the Unit, independent of the surrounding
* environment.
@@ -84,6 +85,8 @@ Source: screens/unit/renderer/UnitRenderer.js
// We need to know the Keyboard state in order to show or hide elements.
// For example: In "editing" mode of a writing item we want to hide the "check" button.
useKeyboardVisibilityHandler(({ status }) => {
+ debug('keyboard visibility changed', status)
+
if (status === 'shown') {
setKeyboardStatus('shown')
}
@@ -127,10 +130,12 @@ Source: screens/unit/renderer/UnitRenderer.js
if (allTrue) {
Vibration.vibrate(500)
scrollViewRef.current?.scrollToEnd({ animated: true })
+ TTSengine.stop()
await Sound.play(RIGHT_ANSWER)
}
else {
Vibration.vibrate(100)
+ TTSengine.stop()
await Sound.play(WRONG_ANSWER)
}
}
@@ -154,7 +159,6 @@ Source: screens/unit/renderer/UnitRenderer.js
color={Colors.gray}
size={10}
name='info'
- type='font-awesome-5'
/>
</View>
<InstructionsGraphicsRenderer
@@ -181,7 +185,6 @@ Source: screens/unit/renderer/UnitRenderer.js
color={Colors.success}
size={20}
name='thumbs-up'
- type='font-awesome-5'
/>
</View>
)
@@ -199,54 +202,59 @@ Source: screens/unit/renderer/UnitRenderer.js
}
return (
- <ScrollView
- ref={scrollViewRef}
- onMomentumScrollEnd={updateLastScrollPos}
- contentContainerStyle={styles.scrollView}
- persistentScrollbar
- keyboardShouldPersistTaps='always'
- >
- {/* 1. PART STIMULI */}
- <FadePanel style={mergeStyles(unitCardStyles, dropShadow)} visible={fadeIn >= 0}>
- <PureContentRenderer
- elements={unitDoc.stimuli}
- keyPrefix={`${unitId}-stimuli`}
- dimensionColor={dimensionColor}
- />
- </FadePanel>
-
- {/* 2. PART INSTRUCTIONS */}
- {renderInstructions()}
-
- {/* 3. PART TASK PAGE CONTENT */}
- <FadePanel
- style={{ ...unitCardStyles, borderWidth: 3, borderColor: Colors.gray, paddingTop: 0, paddingBottom: 20 }}
- visible={fadeIn >= 2}
+ <KeyboardAvoidingView keyboardVerticalOffset={50} behavior={isIOS() ? 'padding' : 'position'}>
+ <ScrollView
+ ref={scrollViewRef}
+ onMomentumScrollEnd={updateLastScrollPos}
+ contentContainerStyle={styles.scrollView}
+ persistentScrollbar
+ keyboardDismissMode='none'
+ contentInset={{ bottom: 20 }}
+ keyboardShouldPersistTaps='always'
+ automaticallyAdjustKeyboardInsets
>
- <LeaText style={styles.pageText}>{page + 1} / {unitDoc.pages.length}</LeaText>
-
- <PureContentRenderer
- elements={unitDoc.pages[page]?.content}
- keyPrefix={`${unitId}-${page}`}
- scoreResult={showCorrectResponse && scoreResult}
- showCorrectResponse={showCorrectResponse}
- dimensionColor={dimensionColor}
- submitResponse={submitResponse}
- />
- </FadePanel>
+ {/* 1. PART STIMULI */}
+ <FadePanel style={mergeStyles(unitCardStyles, dropShadow)} visible={fadeIn >= 0}>
+ <PureContentRenderer
+ elements={unitDoc.stimuli}
+ keyPrefix={`${unitId}-stimuli`}
+ dimensionColor={dimensionColor}
+ />
+ </FadePanel>
+
+ {/* 2. PART INSTRUCTIONS */}
+ {renderInstructions()}
+
+ {/* 3. PART TASK PAGE CONTENT */}
+ <FadePanel
+ style={{ ...unitCardStyles, borderWidth: 3, borderColor: Colors.gray, paddingTop: 0, paddingBottom: 20 }}
+ visible={fadeIn >= 2}
+ >
+ <LeaText style={styles.pageText}>{page + 1} / {unitDoc.pages.length}</LeaText>
+
+ <PureContentRenderer
+ elements={unitDoc.pages[page]?.content}
+ keyPrefix={`${unitId}-${page}`}
+ scoreResult={showCorrectResponse && scoreResult}
+ showCorrectResponse={showCorrectResponse}
+ dimensionColor={dimensionColor}
+ submitResponse={submitResponse}
+ />
+ </FadePanel>
- {renderAllTrue()}
+ {renderAllTrue()}
- {renderFooter()}
- </ScrollView>
+ {renderFooter()}
+ </ScrollView>
+ </KeyboardAvoidingView>
)
}
const RIGHT_ANSWER = 'rightAnswer'
const WRONG_ANSWER = 'wrongAnswer'
-Sound.load(RIGHT_ANSWER, () => require('../../../assets/audio/right_answer.wav'))
-Sound.load(WRONG_ANSWER, () => require('../../../assets/audio/wrong_answer.mp3'))
+Sound.load(RIGHT_ANSWER, () => require('../../../../assets/audio/right_answer.wav'))
+Sound.load(WRONG_ANSWER, () => require('../../../../assets/audio/wrong_answer.mp3'))
const styles = createStyleSheet({
instructionStyles: {
@@ -301,13 +309,13 @@ Source: screens/unit/renderer/UnitRenderer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_renderer_UnitSetRenderer.js.html b/docs/api/app/screens_unit_renderer_UnitSetRenderer.js.html
index ba1e6bad..c5d7fb36 100644
--- a/docs/api/app/screens_unit_renderer_UnitSetRenderer.js.html
+++ b/docs/api/app/screens_unit_renderer_UnitSetRenderer.js.html
@@ -73,13 +73,13 @@ Source: screens/unit/renderer/UnitSetRenderer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/screens_unit_shouldRenderStory.js.html b/docs/api/app/screens_unit_shouldRenderStory.js.html
index 166a4e5c..1aea01c8 100644
--- a/docs/api/app/screens_unit_shouldRenderStory.js.html
+++ b/docs/api/app/screens_unit_shouldRenderStory.js.html
@@ -57,13 +57,13 @@ Source: screens/unit/shouldRenderStory.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/state_AppSession.js.html b/docs/api/app/state_AppSession.js.html
index c03cb8ed..96f836e0 100644
--- a/docs/api/app/state_AppSession.js.html
+++ b/docs/api/app/state_AppSession.js.html
@@ -144,13 +144,13 @@ Source: state/AppSession.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/state_createStorageAPI.js.html b/docs/api/app/state_createStorageAPI.js.html
index f37b0adc..7082e995 100644
--- a/docs/api/app/state_createStorageAPI.js.html
+++ b/docs/api/app/state_createStorageAPI.js.html
@@ -112,13 +112,13 @@ Source: state/createStorageAPI.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/styles_createStyleSheet.js.html b/docs/api/app/styles_createStyleSheet.js.html
index 0e50f2ec..f2c92940 100644
--- a/docs/api/app/styles_createStyleSheet.js.html
+++ b/docs/api/app/styles_createStyleSheet.js.html
@@ -60,13 +60,13 @@ Source: styles/createStyleSheet.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/tts_TTSVoiceConfig.js.html b/docs/api/app/tts_TTSVoiceConfig.js.html
index 5761956c..b3e8aed2 100644
--- a/docs/api/app/tts_TTSVoiceConfig.js.html
+++ b/docs/api/app/tts_TTSVoiceConfig.js.html
@@ -26,17 +26,19 @@ Source: tts/TTSVoiceConfig.js
- import React, { useState } from 'react'
+ import React, { useEffect, useState } from 'react'
import { useVoices } from '../hooks/useVoices'
import { View } from 'react-native'
import { Loading } from '../components/Loading'
import { createStyleSheet } from '../styles/createStyleSheet'
import { Layout } from '../constants/Layout'
-import { LeaButtonGroup } from '../components/LeaButtonGroup'
import { TTSengine } from '../components/Tts'
import { useTranslation } from 'react-i18next'
import { InteractionGraph } from '../infrastructure/log/InteractionGraph'
import { isIOS } from '../utils/isIOS'
+import { LeaButton } from '../components/LeaButton'
+import { Colors } from '../constants/Colors'
+import { mergeStyles } from '../styles/mergeStyles'
/**
* Allows to set the voice for tts.
@@ -49,7 +51,17 @@ Source: tts/TTSVoiceConfig.js
export const TTSVoiceConfig = props => {
const { t } = useTranslation()
const { voices, voicesLoaded, currentVoice } = useVoices()
- const [selected, setSelected] = useState(false)
+ const [selected, setSelected] = useState(-1)
+
+ // set a default voice
+ useEffect(() => {
+ if (selected === -1) {
+ const index = voices.findIndex(v => v.identifier === currentVoice)
+ if (index > -1) {
+ setSelected(index)
+ }
+ }
+ }, [voices, currentVoice])
if (!voicesLoaded) {
return (
@@ -66,15 +78,15 @@ Source: tts/TTSVoiceConfig.js
}
const justNumbers = voices.length > 3
- const handleChange = (_, index) => {
+ const handleChange = (index) => {
const voice = voices[index]
const text = isIOS()
? voice.name
: getName({ voice, index, justNumbers: false, t })
setNewVoice({ voice, text })
- if (!selected) {
- setSelected(true)
+ if (selected !== index) {
+ setSelected(index)
}
if (props.onChange) {
@@ -82,24 +94,29 @@ Source: tts/TTSVoiceConfig.js
}
}
- const groupData = voices.map((voice, index) => {
- return getName({ voice, index, justNumbers, t })
- })
-
// if we haven't selected anything yet but
// there is an initial set voice
// we set its index to being active
- const activeIndex = selected
- ? null
- : voices.findIndex(voice => voice.identifier === currentVoice)
-
return (
- <LeaButtonGroup
- active={activeIndex}
- data={groupData}
- style={props.style}
- onPress={handleChange}
- />
+ <View style={props.style}>
+ {voices.map((voice, index) => {
+ const name = getName({ voice, index, justNumbers, t })
+ const buttonStyle = {
+ backgroundColor: index === selected ? Colors.primary : Colors.white
+ }
+ return (
+ <View style={styles.container} key={index}>
+ <LeaButton
+ title={name}
+ onPress={() => handleChange(index)}
+ icon='user'
+ color={index === selected ? Colors.white : Colors.primary}
+ buttonStyle={mergeStyles(styles.entry, buttonStyle)}
+ />
+ </View>
+ )
+ })}
+ </View>
)
}
@@ -120,9 +137,11 @@ Source: tts/TTSVoiceConfig.js
}
const value = index + 1
+ const plain = t('tts.voice', { value })
+
const name = justNumbers
- ? String(value)
- : t('tts.voice', { value })
+ ? plain
+ : t('tts.hello', { name: plain })
return name
}
@@ -148,7 +167,16 @@ Source: tts/TTSVoiceConfig.js
}
const styles = createStyleSheet({
- container: Layout.container()
+ container: {
+ ...Layout.container(),
+ margin: 20
+ },
+ row: {
+ flex: 1
+ },
+ entry: {
+ padding: 20
+ }
})
@@ -160,13 +188,13 @@ Source: tts/TTSVoiceConfig.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_array_byDocId.js.html b/docs/api/app/utils_array_byDocId.js.html
index ffcdce54..d5c5a329 100644
--- a/docs/api/app/utils_array_byDocId.js.html
+++ b/docs/api/app/utils_array_byDocId.js.html
@@ -43,13 +43,13 @@ Source: utils/array/byDocId.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_array_byOrderedIds.js.html b/docs/api/app/utils_array_byOrderedIds.js.html
index ad33ba6b..630c619b 100644
--- a/docs/api/app/utils_array_byOrderedIds.js.html
+++ b/docs/api/app/utils_array_byOrderedIds.js.html
@@ -53,13 +53,13 @@ Source: utils/array/byOrderedIds.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_array_randomArrayElement.js.html b/docs/api/app/utils_array_randomArrayElement.js.html
index d92bdd69..ea44b6a2 100644
--- a/docs/api/app/utils_array_randomArrayElement.js.html
+++ b/docs/api/app/utils_array_randomArrayElement.js.html
@@ -58,13 +58,13 @@ Source: utils/array/randomArrayElement.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_array_toArrayIfNot.js.html b/docs/api/app/utils_array_toArrayIfNot.js.html
index bf19fff9..3a2ad3f0 100644
--- a/docs/api/app/utils_array_toArrayIfNot.js.html
+++ b/docs/api/app/utils_array_toArrayIfNot.js.html
@@ -50,13 +50,13 @@ Source: utils/array/toArrayIfNot.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_array_toDocId.js.html b/docs/api/app/utils_array_toDocId.js.html
index 80e0cf72..cdbc4f29 100644
--- a/docs/api/app/utils_array_toDocId.js.html
+++ b/docs/api/app/utils_array_toDocId.js.html
@@ -42,13 +42,13 @@ Source: utils/array/toDocId.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_asyncTimeout.js.html b/docs/api/app/utils_asyncTimeout.js.html
index b4323d74..474eab2b 100644
--- a/docs/api/app/utils_asyncTimeout.js.html
+++ b/docs/api/app/utils_asyncTimeout.js.html
@@ -48,13 +48,13 @@ Source: utils/asyncTimeout.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_createTimedPromise.js.html b/docs/api/app/utils_createTimedPromise.js.html
index 8ffcced9..79b5f820 100644
--- a/docs/api/app/utils_createTimedPromise.js.html
+++ b/docs/api/app/utils_createTimedPromise.js.html
@@ -47,7 +47,7 @@ Source: utils/createTimedPromise.js
* @param details {*=} optional any detail attached to the error context for better error tracing
* @return {Promise<Awaited<unknown>>}
*/
-export const createTimedPromise = (promise, { timeout = 1000, throwIfTimedOut = false, message, details } = {}) => {
+export const createTimedPromise = (promise, { timeout = 5000, throwIfTimedOut = false, message, details } = {}) => {
let timeOut
const race = Promise.race([
@@ -81,13 +81,13 @@ Source: utils/createTimedPromise.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_math_randomIntInclusive.js.html b/docs/api/app/utils_math_randomIntInclusive.js.html
index 9b2d0e56..1a69bc6e 100644
--- a/docs/api/app/utils_math_randomIntInclusive.js.html
+++ b/docs/api/app/utils_math_randomIntInclusive.js.html
@@ -53,13 +53,13 @@ Source: utils/math/randomIntInclusive.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_number_isSafeInteger.js.html b/docs/api/app/utils_number_isSafeInteger.js.html
index c5b084bb..e5cb710a 100644
--- a/docs/api/app/utils_number_isSafeInteger.js.html
+++ b/docs/api/app/utils_number_isSafeInteger.js.html
@@ -48,13 +48,13 @@ Source: utils/number/isSafeInteger.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_number_isValidNumber.js.html b/docs/api/app/utils_number_isValidNumber.js.html
index 019ff958..288c4e43 100644
--- a/docs/api/app/utils_number_isValidNumber.js.html
+++ b/docs/api/app/utils_number_isValidNumber.js.html
@@ -55,13 +55,13 @@ Source: utils/number/isValidNumber.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_number_toInteger.js.html b/docs/api/app/utils_number_toInteger.js.html
index a8d515a5..978feb25 100644
--- a/docs/api/app/utils_number_toInteger.js.html
+++ b/docs/api/app/utils_number_toInteger.js.html
@@ -44,13 +44,13 @@ Source: utils/number/toInteger.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_number_toPrecisionNumber.js.html b/docs/api/app/utils_number_toPrecisionNumber.js.html
index 0c67fb59..5706f33c 100644
--- a/docs/api/app/utils_number_toPrecisionNumber.js.html
+++ b/docs/api/app/utils_number_toPrecisionNumber.js.html
@@ -45,13 +45,13 @@ Source: utils/number/toPrecisionNumber.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_object_clearObject.js.html b/docs/api/app/utils_object_clearObject.js.html
index 89e40bb4..9f772bbe 100644
--- a/docs/api/app/utils_object_clearObject.js.html
+++ b/docs/api/app/utils_object_clearObject.js.html
@@ -55,13 +55,13 @@ Source: utils/object/clearObject.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_object_hasOwnProp.js.html b/docs/api/app/utils_object_hasOwnProp.js.html
index 7fbc5b49..8cc7bdbb 100644
--- a/docs/api/app/utils_object_hasOwnProp.js.html
+++ b/docs/api/app/utils_object_hasOwnProp.js.html
@@ -43,13 +43,13 @@ Source: utils/object/hasOwnProp.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_object_isDefined.js.html b/docs/api/app/utils_object_isDefined.js.html
index f80af8d6..e223fe85 100644
--- a/docs/api/app/utils_object_isDefined.js.html
+++ b/docs/api/app/utils_object_isDefined.js.html
@@ -42,13 +42,13 @@ Source: utils/object/isDefined.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_simpleRandomHex.js.html b/docs/api/app/utils_simpleRandomHex.js.html
index ba8fa0c3..42c14e85 100644
--- a/docs/api/app/utils_simpleRandomHex.js.html
+++ b/docs/api/app/utils_simpleRandomHex.js.html
@@ -47,13 +47,13 @@ Source: utils/simpleRandomHex.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_text_createSimpleTokenizer.js.html b/docs/api/app/utils_text_createSimpleTokenizer.js.html
index b95969b9..cdd10bc0 100644
--- a/docs/api/app/utils_text_createSimpleTokenizer.js.html
+++ b/docs/api/app/utils_text_createSimpleTokenizer.js.html
@@ -82,13 +82,13 @@ Source: utils/text/createSimpleTokenizer.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_text_isWord.js.html b/docs/api/app/utils_text_isWord.js.html
index bc1ad7ab..7093d557 100644
--- a/docs/api/app/utils_text_isWord.js.html
+++ b/docs/api/app/utils_text_isWord.js.html
@@ -42,13 +42,13 @@ Source: utils/text/isWord.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
diff --git a/docs/api/app/utils_trigonometry_getPositionOnCircle.js.html b/docs/api/app/utils_trigonometry_getPositionOnCircle.js.html
index 62199b9d..d819c2fa 100644
--- a/docs/api/app/utils_trigonometry_getPositionOnCircle.js.html
+++ b/docs/api/app/utils_trigonometry_getPositionOnCircle.js.html
@@ -63,13 +63,13 @@ Source: utils/trigonometry/getPositionOnCircle.js
- Home Classes Global
+ Home Classes Global
- Documentation generated by JSDoc 4.0.2 on Wed Oct 25 2023 09:40:55 GMT+0200 (Central European Summer Time)
+ Documentation generated by JSDoc 4.0.3 on Tue Sep 03 2024 11:47:20 GMT+0200 (Central European Summer Time)
From 360266d2998911e9ec0baf7d9cbac460d2ae664a Mon Sep 17 00:00:00 2001
From: jankapunkt
Date: Tue, 3 Sep 2024 11:49:17 +0200
Subject: [PATCH 3/4] fix(app): standard js lint fix
---
app/__tests__/contexts/MapIcons.tests.js | 2 +-
app/__tests__/contexts/Sync.tests.js | 2 +-
app/__tests__/i18n/i18n.test.js | 2 +-
app/__tests__/screens/map/loadMapData.tests.js | 3 ---
4 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/app/__tests__/contexts/MapIcons.tests.js b/app/__tests__/contexts/MapIcons.tests.js
index 9d35f410..99b36539 100644
--- a/app/__tests__/contexts/MapIcons.tests.js
+++ b/app/__tests__/contexts/MapIcons.tests.js
@@ -1,7 +1,7 @@
import { MapIcons } from '../../lib/contexts/MapIcons'
import { createContextBaseTests } from '../../__testHelpers__/createContextBaseTests'
import { simpleRandomHex } from '../../lib/utils/simpleRandomHex'
-import { render } from '@testing-library/react-native';
+import { render } from '@testing-library/react-native'
describe(MapIcons.name, () => {
createContextBaseTests({ ctx: MapIcons })
diff --git a/app/__tests__/contexts/Sync.tests.js b/app/__tests__/contexts/Sync.tests.js
index a359e515..80f84dad 100644
--- a/app/__tests__/contexts/Sync.tests.js
+++ b/app/__tests__/contexts/Sync.tests.js
@@ -2,7 +2,7 @@ import Meteor from '@meteorrn/core'
import { Sync } from '../../lib/infrastructure/sync/Sync'
import { simpleRandom } from '../../__testHelpers__/simpleRandom'
import { collectionExists } from '../../lib/infrastructure/collections/collections'
-import { restoreAll, stub, overrideStub } from '../../__testHelpers__/stub'
+import { restoreAll, stub } from '../../__testHelpers__/stub'
import { createContextStorage } from '../../lib/contexts/createContextStorage'
import { cleanup } from '@testing-library/react-native'
import { ContextRepository } from '../../lib/infrastructure/ContextRepository'
diff --git a/app/__tests__/i18n/i18n.test.js b/app/__tests__/i18n/i18n.test.js
index c7c7b282..ca985b77 100644
--- a/app/__tests__/i18n/i18n.test.js
+++ b/app/__tests__/i18n/i18n.test.js
@@ -20,7 +20,7 @@ test('recursively iterate all object keys of i18 EN and DE, checks if the same n
})
return keys
}
- const byName = (a,b) => a.localeCompare(b)
+ const byName = (a, b) => a.localeCompare(b)
const deKeys = toKeys(translationDE).sort(byName)
const enKeys = toKeys(translationEN).sort(byName)
diff --git a/app/__tests__/screens/map/loadMapData.tests.js b/app/__tests__/screens/map/loadMapData.tests.js
index 83aae763..c2ed615b 100644
--- a/app/__tests__/screens/map/loadMapData.tests.js
+++ b/app/__tests__/screens/map/loadMapData.tests.js
@@ -1,4 +1,3 @@
-import Meteor from '@meteorrn/core'
import { Dimension } from '../../../lib/contexts/Dimension'
import { stub, restoreAll } from '../../../__testHelpers__/stub'
import { simpleRandom } from '../../../__testHelpers__/simpleRandom'
@@ -32,8 +31,6 @@ describe(loadMapData.name, () => {
it('returns an "empty" message if the server responded with no or faulty map data', async () => {
const fieldDoc = { _id: simpleRandom() }
-
-
const allData = [
undefined,
null,
From 9873a404b7a3246d057dc4f3037c2ef9c728b7b1 Mon Sep 17 00:00:00 2001
From: jankapunkt
Date: Tue, 3 Sep 2024 12:13:03 +0200
Subject: [PATCH 4/4] ci: updated workflows
---
.github/workflows/Audit.yml | 34 ----------------
.github/workflows/app.yml | 66 ++++++++++++++++++++++++++++++
.github/workflows/backend.yml | 69 ++++++++++++++++++--------------
.github/workflows/jest_test.yml | 33 ---------------
.github/workflows/jsdoc_test.yml | 31 --------------
.github/workflows/lint_test.yml | 31 --------------
6 files changed, 106 insertions(+), 158 deletions(-)
delete mode 100644 .github/workflows/Audit.yml
create mode 100644 .github/workflows/app.yml
delete mode 100644 .github/workflows/jest_test.yml
delete mode 100644 .github/workflows/jsdoc_test.yml
delete mode 100644 .github/workflows/lint_test.yml
diff --git a/.github/workflows/Audit.yml b/.github/workflows/Audit.yml
deleted file mode 100644
index c01bb1c1..00000000
--- a/.github/workflows/Audit.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-name: Check for vulnerabilities
-
-on:
- push:
- branches:
- - release
-
- pull_request:
- branches:
- - release
-
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: checkout
- uses: actions/checkout@v3
- - name: setup node
- uses: actions/setup-node@v3
- with:
- node-version: '16.x'
- - name: cache dependencies
- uses: actions/cache@v1
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
-
- - name: open src folder and install node modules
- run: cd src && npm install
- - name: open src folder and check for vulnerabilities
- run: cd src && npm audit --production
diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml
new file mode 100644
index 00000000..fddc5cb2
--- /dev/null
+++ b/.github/workflows/app.yml
@@ -0,0 +1,66 @@
+name: App Testsuite
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ lint:
+ name: 'App Lint'
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+
+ - name: setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'npm'
+ cache-dependency-path: 'package-lock.json'
+
+ - name: install node modules
+ run: cd app && npm i --legacy-peer-deps --force
+ - name: run standard js
+ run: cd app && npm run lint
+
+ tests:
+ name: 'App Unit Tests'
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+
+ - name: setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'npm'
+ cache-dependency-path: 'package-lock.json'
+
+ - name: install node modules
+ run: cd app && npm ci --legacy-peer-deps --force
+
+ - name: run jest tests
+ run: cd app && npm test
+
+ docs:
+ name: 'Build App jsDoc'
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+
+ - name: setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'npm'
+ cache-dependency-path: 'package-lock.json'
+
+ - name: install node modules
+ run: cd app && npm i --legacy-peer-deps --force
+ - name: run jsdoc
+ run: cd app && npm run docs
diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml
index a60139df..d51e75a6 100644
--- a/.github/workflows/backend.yml
+++ b/.github/workflows/backend.yml
@@ -3,7 +3,7 @@ name: Backend Tests
on:
push:
branches:
- - ma
+ - main
pull_request:
jobs:
@@ -11,41 +11,35 @@ jobs:
name: Backend JS lint
runs-on: ubuntu-latest
steps:
- - name: checkout
- uses: actions/checkout@v3
-
- - name: setup node
- uses: actions/setup-node@v3
- with:
- node-version: '14.x'
+ - name: checkout
+ uses: actions/checkout@v4
- - name: cache dependencies
- uses: actions/cache@v3
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
+ - name: setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'npm'
+ cache-dependency-path: 'package-lock.json'
- - run: cd backend && npm install
- - run: cd backend && npm run lint:code
+ - run: cd backend && npm install
+ - run: cd backend && npm run lint:code
tests:
name: Backend Meteor ${{ matrix.meteor }} tests
runs-on: ubuntu-latest
steps:
- name: checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Checkout leaonline:corelib repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: leaonline/corelib
path: github/corelib
- name: Checkout leaonline:service-registry repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: leaonline/service-registry
path: github/service-registry
@@ -53,12 +47,12 @@ jobs:
# CACHING
- name: Install Meteor
id: cache-meteor-install
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.meteor
- key: v2-meteor-${{ hashFiles('.meteor/versions') }}
+ key: v3-meteor-${{ hashFiles('.meteor/versions') }}
restore-keys: |
- v2-meteor-
+ v3-meteor-
- name: Cache NPM dependencies
id: cache-meteor-npm
@@ -67,25 +61,25 @@ jobs:
path: ~/.npm
key: v1-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
- v1-npm-
+ v1-npm-
- name: Cache Meteor build
id: cache-meteor-build
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: |
.meteor/local/resolver-result-cache.json
.meteor/local/plugin-cache
.meteor/local/isopacks
.meteor/local/bundler-cache/scanner
- key: v2-meteor_build_cache-${{ github.ref }}-${{ github.sha }}
+ key: v3-meteor_build_cache-${{ github.ref }}-${{ github.sha }}
restore-key: |
- v2-meteor_build_cache-
+ v3-meteor_build_cache-
- name: Setup meteor
uses: meteorengineer/setup-meteor@v1
with:
- meteor-release: '2.7.3'
+ meteor-release: '3.0.2'
- name: Install NPM Dependencies
run: cd backend && meteor npm ci
@@ -119,4 +113,21 @@ jobs:
# uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
# with:
# path: ".coverage/lcov.info"
-# min_coverage: 95 # TODO increase to 95!
\ No newline at end of file
+# min_coverage: 95 # TODO increase to 95!
+
+ docs:
+ name: Backend Build Docs
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+
+ - name: setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'npm'
+ cache-dependency-path: 'package-lock.json'
+
+ - run: cd backend && npm ci
+ - run: cd backend && npm run build:docs
diff --git a/.github/workflows/jest_test.yml b/.github/workflows/jest_test.yml
deleted file mode 100644
index cbdff835..00000000
--- a/.github/workflows/jest_test.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: App Tests
-
-
-on:
- push:
- branches:
- - main
- pull_request:
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
-
- - name: checkout
- uses: actions/checkout@v3
- - name: setup node
- uses: actions/setup-node@v3
- with:
- node-version: '16.x'
- - name: cache dependencies
- uses: actions/cache@v1
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
-
- - name: install node modules
- run: cd src && npm i --legacy-peer-deps --force
-
- - name: run jest tests
- run: cd src && npm test
diff --git a/.github/workflows/jsdoc_test.yml b/.github/workflows/jsdoc_test.yml
deleted file mode 100644
index 94d9fe7e..00000000
--- a/.github/workflows/jsdoc_test.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: App Build Docs
-
-on:
- push:
- branches:
- - main
-
- pull_request:
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: checkout
- uses: actions/checkout@v3
- - name: setup node
- uses: actions/setup-node@v3
- with:
- node-version: '16.x'
- - name: cache dependencies
- uses: actions/cache@v1
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
-
- - name: install node modules
- run: cd src && npm i --legacy-peer-deps --force
- - name: run jsdoc
- run: cd src && npm run docs
diff --git a/.github/workflows/lint_test.yml b/.github/workflows/lint_test.yml
deleted file mode 100644
index 8a7c2108..00000000
--- a/.github/workflows/lint_test.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: App JS Lint
-
-on:
- push:
- branches:
- - main
-
- pull_request:
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: checkout
- uses: actions/checkout@v3
- - name: setup node
- uses: actions/setup-node@v3
- with:
- node-version: '16.x'
- - name: cache dependencies
- uses: actions/cache@v1
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
-
- - name: install node modules
- run: cd src && npm i --legacy-peer-deps --force
- - name: run standard js
- run: cd src && npm run lint