diff --git a/__tests__/Home-test.tsx b/__tests__/Home-test.tsx
deleted file mode 100644
index d476509..0000000
--- a/__tests__/Home-test.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import 'react-native'
-import React from 'react'
-import Home from '../src/Home'
-
-// Note: test renderer must be required after react-native.
-import renderer from 'react-test-renderer'
-
-it('renders correctly', () => {
- renderer.create()
-})
diff --git a/android/app/build.gradle b/android/app/build.gradle
index e94f36a..7480750 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -95,8 +95,8 @@ android {
applicationId "com.razinj.context_launcher"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 11
- versionName "1.4.7"
+ versionCode 12
+ versionName "1.4.8"
archivesBaseName = "context-launcher-v$versionName-$versionCode"
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2bad944..884b413 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,7 +1,8 @@
-
+
-
+
@@ -11,6 +12,8 @@
@@ -19,7 +22,6 @@
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:excludeFromRecents="true"
android:exported="true"
- android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
@@ -31,9 +33,9 @@
+ android:exported="true">
diff --git a/android/app/src/main/java/com/razinj/context_launcher/AppProvider.java b/android/app/src/main/java/com/razinj/context_launcher/AppProvider.java
index 35826cf..d492cf8 100644
--- a/android/app/src/main/java/com/razinj/context_launcher/AppProvider.java
+++ b/android/app/src/main/java/com/razinj/context_launcher/AppProvider.java
@@ -21,7 +21,7 @@ public class AppProvider extends Service {
@Override
public void onCreate() {
- final LauncherApps launcherApps = (LauncherApps) this.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+ final LauncherApps launcherApps = (LauncherApps) getSystemService(Context.LAUNCHER_APPS_SERVICE);
assert launcherApps != null;
launcherApps.registerCallback(new LauncherAppsCallback() {
@@ -45,20 +45,6 @@ public void onPackageRemoved(String packageName, UserHandle user) {
PackageChangeReceiver.handleEvent(AppProvider.this, Intent.ACTION_PACKAGE_REMOVED, packageName, false);
}
-
- @Override
- public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
- if (user.equals(Process.myUserHandle())) return;
-
- PackageChangeReceiver.handleEvent(AppProvider.this, Intent.ACTION_MEDIA_MOUNTED, null, false);
- }
-
- @Override
- public void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing) {
- if (user.equals(Process.myUserHandle())) return;
-
- PackageChangeReceiver.handleEvent(AppProvider.this, Intent.ACTION_MEDIA_UNMOUNTED, null, false);
- }
});
this.packageChangeReceiver = new PackageChangeReceiver();
@@ -67,43 +53,47 @@ public void onPackagesUnavailable(String[] packageNames, UserHandle user, boolea
appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- appChangedIntentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
- appChangedIntentFilter.addAction(Intent.ACTION_MEDIA_REMOVED);
appChangedIntentFilter.addDataScheme("package");
appChangedIntentFilter.addDataScheme("file");
- this.registerReceiver(packageChangeReceiver, appChangedIntentFilter);
+ registerReceiver(packageChangeReceiver, appChangedIntentFilter);
super.onCreate();
+
startForegroundCustom();
}
private void startForegroundCustom() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- // Notification channel
- String channelId = BuildConfig.APPLICATION_ID;
- String channelName = "AppProvider Service";
- NotificationChannel notificationChannel = new NotificationChannel(
- channelId,
- channelName,
- NotificationManager.IMPORTANCE_NONE
- );
- notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
-
- // Notification manager
- NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- assert notificationManager != null;
- notificationManager.createNotificationChannel(notificationChannel);
-
- // Notification builder
- NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId);
- Notification notification = notificationBuilder
- .setPriority(NotificationManager.IMPORTANCE_MIN)
- .setCategory(Notification.CATEGORY_SERVICE)
- .setOngoing(true)
- .build();
- startForeground(1, notification);
- } else startForeground(2, new Notification());
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ startForeground(1, new Notification());
+ return;
+ }
+
+ // Notification channel
+ String channelId = BuildConfig.APPLICATION_ID;
+ String channelName = "AppProvider Channel";
+ NotificationChannel notificationChannel = new NotificationChannel(
+ channelId,
+ channelName,
+ NotificationManager.IMPORTANCE_NONE
+ );
+ notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+
+ // Notification manager
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ assert notificationManager != null;
+ notificationManager.createNotificationChannel(notificationChannel);
+
+ // Notification builder
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId);
+ Notification notification = notificationBuilder
+ .setPriority(NotificationManager.IMPORTANCE_NONE)
+ .setCategory(Notification.CATEGORY_SERVICE)
+ .setAutoCancel(false)
+ .setOngoing(true)
+ .setSilent(true)
+ .build();
+ startForeground(1, notification);
}
@Override
diff --git a/android/app/src/main/java/com/razinj/context_launcher/MainApplication.java b/android/app/src/main/java/com/razinj/context_launcher/MainApplication.java
index 99951b7..dd8cc23 100644
--- a/android/app/src/main/java/com/razinj/context_launcher/MainApplication.java
+++ b/android/app/src/main/java/com/razinj/context_launcher/MainApplication.java
@@ -1,6 +1,8 @@
package com.razinj.context_launcher;
import android.app.Application;
+import android.content.Intent;
+import android.os.Build;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
@@ -57,5 +59,11 @@ public void onCreate() {
DefaultNewArchitectureEntryPoint.load();
}
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(new Intent(this, AppProvider.class));
+ } else {
+ startService(new Intent(this, AppProvider.class));
+ }
}
}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.java b/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.java
index 9495eb5..6869f94 100644
--- a/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.java
+++ b/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.java
@@ -7,9 +7,9 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.util.Log;
public class PackageChangeReceiver extends BroadcastReceiver {
+
@Override
public void onReceive(Context context, Intent intent) {
String packageName = intent.getData().getSchemeSpecificPart();
@@ -34,10 +34,10 @@ public static void handleEvent(Context context, String action, String packageNam
if (launchIntent == null) return;
intent.putExtra(PACKAGE_CHANGE_IS_REMOVED, Boolean.FALSE);
- } else if (!replacing) {
- intent.putExtra(PACKAGE_CHANGE_IS_REMOVED, Boolean.TRUE);
- } else {
+ } else if (replacing) {
intent.putExtra(PACKAGE_CHANGE_IS_REMOVED, Boolean.FALSE);
+ } else {
+ intent.putExtra(PACKAGE_CHANGE_IS_REMOVED, Boolean.TRUE);
}
context.sendBroadcast(intent);
diff --git a/android/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..477159f
--- /dev/null
+++ b/android/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index a51ce62..a4edf28 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "context_launcher",
- "version": "1.4.7",
+ "version": "1.4.8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "context_launcher",
- "version": "1.4.7",
+ "version": "1.4.8",
"dependencies": {
"@gorhom/bottom-sheet": "^4.4.5",
"@react-native-async-storage/async-storage": "^1.17.11",
@@ -25,12 +25,12 @@
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.13",
"@react-native-community/eslint-config": "^2.0.0",
+ "@testing-library/react-native": "^11.5.0",
"@tsconfig/react-native": "^2.0.2",
"@types/jest": "^29.2.1",
"@types/react": "^18.0.24",
"@types/react-native": "^0.71.0",
"@types/react-native-vector-icons": "^6.4.10",
- "@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"babel-jest": "^29.2.1",
@@ -41,7 +41,6 @@
"jest": "^29.2.1",
"metro-react-native-babel-preset": "0.74.1",
"prettier": "^2.4.1",
- "react-test-renderer": "18.2.0",
"typescript": "4.9.4"
}
},
@@ -5166,6 +5165,58 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "node_modules/@testing-library/react-native": {
+ "version": "11.5.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-11.5.0.tgz",
+ "integrity": "sha512-seV+qebsbX4E5CWk/wizU1+2wVLsPyqEzG7sTgrhJ81cgAawg7ay06fIZR9IS75pDeWn2KZVd4mGk1pjJ3i3Zw==",
+ "dev": true,
+ "dependencies": {
+ "pretty-format": "^29.0.0"
+ },
+ "peerDependencies": {
+ "jest": ">=28.0.0",
+ "react": ">=16.0.0",
+ "react-native": ">=0.59",
+ "react-test-renderer": ">=16.0.0"
+ },
+ "peerDependenciesMeta": {
+ "jest": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@testing-library/react-native/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@testing-library/react-native/node_modules/pretty-format": {
+ "version": "29.3.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz",
+ "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.0.0",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@testing-library/react-native/node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
"node_modules/@tsconfig/react-native": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/react-native/-/react-native-2.0.3.tgz",
@@ -5365,15 +5416,6 @@
"@types/react": "*"
}
},
- "node_modules/@types/react-test-renderer": {
- "version": "18.0.0",
- "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz",
- "integrity": "sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ==",
- "dev": true,
- "dependencies": {
- "@types/react": "*"
- }
- },
"node_modules/@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
@@ -15190,6 +15232,7 @@
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz",
"integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==",
"dev": true,
+ "peer": true,
"dependencies": {
"react-is": "^18.2.0",
"react-shallow-renderer": "^16.15.0",
@@ -15203,7 +15246,8 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/readable-stream": {
"version": "3.6.0",
@@ -20889,6 +20933,40 @@
"@sinonjs/commons": "^1.7.0"
}
},
+ "@testing-library/react-native": {
+ "version": "11.5.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-11.5.0.tgz",
+ "integrity": "sha512-seV+qebsbX4E5CWk/wizU1+2wVLsPyqEzG7sTgrhJ81cgAawg7ay06fIZR9IS75pDeWn2KZVd4mGk1pjJ3i3Zw==",
+ "dev": true,
+ "requires": {
+ "pretty-format": "^29.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "29.3.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz",
+ "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==",
+ "dev": true,
+ "requires": {
+ "@jest/schemas": "^29.0.0",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ }
+ },
+ "react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ }
+ }
+ },
"@tsconfig/react-native": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/react-native/-/react-native-2.0.3.tgz",
@@ -21083,15 +21161,6 @@
}
}
},
- "@types/react-test-renderer": {
- "version": "18.0.0",
- "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz",
- "integrity": "sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ==",
- "dev": true,
- "requires": {
- "@types/react": "*"
- }
- },
"@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
@@ -28436,6 +28505,7 @@
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz",
"integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==",
"dev": true,
+ "peer": true,
"requires": {
"react-is": "^18.2.0",
"react-shallow-renderer": "^16.15.0",
@@ -28446,7 +28516,8 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
- "dev": true
+ "dev": true,
+ "peer": true
}
}
},
diff --git a/package.json b/package.json
index 2f0f01c..87e9e47 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "context_launcher",
- "version": "1.4.7",
+ "version": "1.4.8",
"private": false,
"scripts": {
"android": "react-native run-android",
@@ -29,12 +29,12 @@
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.13",
"@react-native-community/eslint-config": "^2.0.0",
+ "@testing-library/react-native": "^11.5.0",
"@tsconfig/react-native": "^2.0.2",
"@types/jest": "^29.2.1",
"@types/react": "^18.0.24",
"@types/react-native": "^0.71.0",
"@types/react-native-vector-icons": "^6.4.10",
- "@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"babel-jest": "^29.2.1",
@@ -45,21 +45,12 @@
"jest": "^29.2.1",
"metro-react-native-babel-preset": "0.74.1",
"prettier": "^2.4.1",
- "react-test-renderer": "18.2.0",
"typescript": "4.9.4"
},
"resolutions": {
- "@types/react": "^17"
+ "@types/react": "^18"
},
"jest": {
- "preset": "react-native",
- "moduleFileExtensions": [
- "ts",
- "tsx",
- "js",
- "jsx",
- "json",
- "node"
- ]
+ "preset": "react-native"
}
}
diff --git a/src/components/AppItem.tsx b/src/components/AppItem.tsx
index 4a0cea6..332622d 100644
--- a/src/components/AppItem.tsx
+++ b/src/components/AppItem.tsx
@@ -9,7 +9,7 @@ import { useDispatch } from 'react-redux'
import { addRecentApp } from '../slices/recentApps'
import { resetAppsSearchState } from '../slices/appsSearch'
// Utils
-import { launchApp } from '../utils/appsModule'
+import { launchApp } from '../utils/apps-module'
// Native modules
import AppsModule from '../native-modules/AppsModule'
// Contexts
diff --git a/src/components/AppItemMenu.tsx b/src/components/AppItemMenu.tsx
index f5c7cb8..47d4d1d 100644
--- a/src/components/AppItemMenu.tsx
+++ b/src/components/AppItemMenu.tsx
@@ -21,7 +21,7 @@ import { BottomSheetModalProvider, BottomSheetModal } from '@gorhom/bottom-sheet
// Icon
import Icon from 'react-native-vector-icons/MaterialIcons'
// Utils
-import { requestAppUninstall, showAppDetails } from '../utils/appsModule'
+import { requestAppUninstall, showAppDetails } from '../utils/apps-module'
// Model
import { FavoriteApp } from '../models/favorite-app'
diff --git a/src/components/Settings/SettingsBottomSheet.tsx b/src/components/Settings/SettingsBottomSheet.tsx
index d61c20d..7104e40 100644
--- a/src/components/Settings/SettingsBottomSheet.tsx
+++ b/src/components/Settings/SettingsBottomSheet.tsx
@@ -21,7 +21,7 @@ import { BottomSheetModal, BottomSheetModalProvider } from '@gorhom/bottom-sheet
// Icon
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
// Utils
-import { showAppDetails } from '../../utils/appsModule'
+import { showAppDetails } from '../../utils/apps-module'
// Constants
import {
PRIMARY_HEX_COLOR,
diff --git a/src/slices/appsList.ts b/src/slices/appsList.ts
index d585e0e..774eb2c 100644
--- a/src/slices/appsList.ts
+++ b/src/slices/appsList.ts
@@ -3,7 +3,7 @@ import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit'
// Constants
import { CONTEXT_LAUNCHER_APP_ID } from '../constants'
// Utils
-import { getAppsLetterIndex } from '../utils/alphabetList'
+import { getAppsLetterIndex } from '../utils/alphabet-list'
// Models
import { RootState } from '../store'
import { AppDetails } from '../models/app-details'
diff --git a/src/utils/alphabet-list.test.ts b/src/utils/alphabet-list.test.ts
new file mode 100644
index 0000000..00eb09f
--- /dev/null
+++ b/src/utils/alphabet-list.test.ts
@@ -0,0 +1,91 @@
+import { getAppsLetterIndex, getFirstLetter, getLettersMap } from './alphabet-list'
+import { AppLetterIndex } from '../models/list-letter-index'
+import { AppDetails } from '../models/app-details'
+
+describe('AlphabetList Tests', () => {
+ describe('getLettersMap Tests', () => {
+ it('should map letters to indexes correctly', () => {
+ const result = getLettersMap()
+
+ expect(result['A']).toEqual(1)
+ expect(result['Z']).toEqual(26)
+ })
+ })
+
+ describe('getFirstLetter Tests', () => {
+ it('should return the correct letter from passed alphabet-starting value', () => {
+ expect(getFirstLetter('hello world!')).toBe('H')
+ })
+
+ it('should return # from passed non-alphabet-starting value', () => {
+ expect(getFirstLetter('_a_value')).toBe('#')
+ })
+ })
+
+ describe('getAppsLetterIndex Tests', () => {
+ const apps: AppDetails[] = [
+ {
+ label: 'App 1',
+ name: 'com.app_1',
+ },
+ {
+ label: 'App 2',
+ name: 'com.app_2',
+ },
+ {
+ label: 'Brave',
+ name: 'com.brave',
+ },
+ {
+ label: 'Chrome',
+ name: 'com.chrome',
+ },
+ {
+ label: 'Clock',
+ name: 'com.clock',
+ },
+ {
+ label: 'Google',
+ name: 'com.google',
+ },
+ {
+ label: 'Youtube',
+ name: 'com.youtube',
+ },
+ {
+ label: '_App',
+ name: 'com._app',
+ },
+ ]
+ const appLetterIndex: AppLetterIndex[] = [
+ {
+ letter: 'A',
+ index: 0,
+ },
+ {
+ letter: 'B',
+ index: 2,
+ },
+ {
+ letter: 'C',
+ index: 3,
+ },
+ {
+ letter: 'G',
+ index: 5,
+ },
+ {
+ letter: 'Y',
+ index: 6,
+ },
+ {
+ letter: '#',
+ index: 7,
+ },
+ ]
+
+ it(`should return correct list of letter/index objects from app's list`, () => {
+ expect(getAppsLetterIndex(apps)).toEqual(appLetterIndex)
+ })
+ })
+})
diff --git a/src/utils/alphabetList.ts b/src/utils/alphabet-list.ts
similarity index 79%
rename from src/utils/alphabetList.ts
rename to src/utils/alphabet-list.ts
index 18adc4a..3447a31 100644
--- a/src/utils/alphabetList.ts
+++ b/src/utils/alphabet-list.ts
@@ -35,7 +35,10 @@ const alphabet = [
'Z',
]
-const getLettersMap = (): LetterMap => {
+/**
+ * @returns Indexed alphabet letters in an object where the key is the alphabet and the value is the index
+ */
+export const getLettersMap = (): LetterMap => {
const letterMap: LetterMap = {}
alphabet.forEach((letter: string, index: number) => (letterMap[letter] = index + 1))
@@ -43,6 +46,10 @@ const getLettersMap = (): LetterMap => {
return letterMap
}
+/**
+ * @param value Any string value
+ * @returns A letter if the first chareceter of the value is an alphabet, otherwise it returns a '#'
+ */
export const getFirstLetter = (value: string): string => {
const firstChar = value.trim().substring(0, 1).toUpperCase()
const isValidLetter = getLettersMap()[firstChar]
diff --git a/src/utils/apps-module.test.ts b/src/utils/apps-module.test.ts
new file mode 100644
index 0000000..72bbbe0
--- /dev/null
+++ b/src/utils/apps-module.test.ts
@@ -0,0 +1,49 @@
+import { launchApp, requestAppUninstall, showAppDetails } from './apps-module'
+import AppsModule from '../native-modules/AppsModule'
+
+jest.mock('react-native', () => {
+ const ReactNative = jest.requireActual('react-native')
+
+ ReactNative.NativeModules.AppsModule = {
+ launchApplication: jest.fn(),
+ showApplicationDetails: jest.fn(),
+ requestApplicationUninstall: jest.fn(),
+ }
+
+ return ReactNative
+})
+
+describe('AppsModule Tests', () => {
+ const aPackageName = 'com.a_package_name'
+
+ beforeAll(() => {
+ jest.spyOn(AppsModule, 'launchApplication')
+ jest.spyOn(AppsModule, 'showApplicationDetails')
+ jest.spyOn(AppsModule, 'requestApplicationUninstall')
+ })
+
+ beforeEach(() => {
+ jest.resetAllMocks()
+ })
+
+ it('should call AppsModule.launchApplication with package name', () => {
+ launchApp(aPackageName)
+
+ expect(AppsModule.launchApplication).toBeCalledTimes(1)
+ expect(AppsModule.launchApplication).toBeCalledWith(aPackageName)
+ })
+
+ it('should call AppsModule.showApplicationDetails with package name', () => {
+ showAppDetails(aPackageName)
+
+ expect(AppsModule.showApplicationDetails).toBeCalledTimes(1)
+ expect(AppsModule.showApplicationDetails).toBeCalledWith(aPackageName)
+ })
+
+ it('should call AppsModule.requestApplicationUninstall with package name', () => {
+ requestAppUninstall(aPackageName)
+
+ expect(AppsModule.requestApplicationUninstall).toBeCalledTimes(1)
+ expect(AppsModule.requestApplicationUninstall).toBeCalledWith(aPackageName)
+ })
+})
diff --git a/src/utils/appsModule.ts b/src/utils/apps-module.ts
similarity index 100%
rename from src/utils/appsModule.ts
rename to src/utils/apps-module.ts
diff --git a/src/utils/keyboard.test.ts b/src/utils/keyboard.test.ts
new file mode 100644
index 0000000..a0d9571
--- /dev/null
+++ b/src/utils/keyboard.test.ts
@@ -0,0 +1,14 @@
+import { Keyboard } from 'react-native'
+import { dismissKeyboard } from './keyboard'
+
+describe('Keyboard Tests', () => {
+ beforeAll(() => {
+ jest.spyOn(Keyboard, 'dismiss')
+ })
+
+ it('should call keyboard dismiss method', () => {
+ dismissKeyboard()
+
+ expect(Keyboard.dismiss).toBeCalledTimes(1)
+ })
+})