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) + }) +})