diff --git a/.github/workflows/compare_translations.py b/.github/workflows/compare_translations.py new file mode 100644 index 0000000000..d8c0aff797 --- /dev/null +++ b/.github/workflows/compare_translations.py @@ -0,0 +1,168 @@ +""" +Script to encourage more efficient coding practices. + +Methodology: + + Utility for comparing translations between default and other languages. + + This module defines a function to compare two translations + and print any missing keys in the other language's translation. +Attributes: + + FileTranslation : Named tuple to represent a combination of file and missing translations. + + Fields: + - file (str): The file name. + - missing_translations (list): List of missing translations. + +Functions: + compare_translations(default_translation, other_translation): + Compare two translations and print missing keys. + + load_translation(filepath): + Load translation from a file. + + check_translations(): + Load the default translation and compare it with other translations. + + main(): + The main function to run the script. + Parses command-line arguments, checks for the existence of the specified directory, + and then calls check_translations with the provided or default directory. + + +Usage: + This script can be executed to check and print missing + translations in other languages based on the default English translation. + +Example: + python compare_translations.py +NOTE: + This script complies with our python3 coding and documentation standards + and should be used as a reference guide. It complies with: + + 1) Pylint + 2) Pydocstyle + 3) Pycodestyle + 4) Flake8 + +""" +# standard imports +import argparse +import json +import os +import sys +from collections import namedtuple + +# Named tuple for file and missing translations combination +FileTranslation = namedtuple("FileTranslation", ["file", "missing_translations"]) + + +def compare_translations(default_translation, other_translation, default_file, other_file): + """Compare two translations and return detailed info about missing/mismatched keys. + + Args: + default_translation (dict): The default translation (en.json). + other_translation (dict): The other language translation. + default_file (str): The name of the default translation file. + other_file (str): The name of the other translation file. + + Returns: + list: A list of detailed error messages for each missing/mismatched key. + """ + errors = [] + + # Check for missing keys in other_translation + for key in default_translation: + if key not in other_translation: + error_msg = f"Missing Key: '{key}' - This key from '{default_file}' is missing in '{other_file}'." + errors.append(error_msg) + + # Check for keys in other_translation that don't match any in default_translation + for key in other_translation: + if key not in default_translation: + error_msg = f"Error Key: '{key}' - This key in '{other_file}' does not match any key in '{default_file}'." + errors.append(error_msg) + + return errors + + + + +def load_translation(filepath): + """Load translation from a file. + + Args: + filepath: Path to the translation file + + Returns: + translation: Loaded translation + """ + with open(filepath, "r", encoding="utf-8") as file: + translation = json.load(file) + return translation + + + +def check_translations(directory): + """Load default translation and compare with other translations. + + Args: + directory (str): The directory containing translation files. + + Returns: + None + """ + default_file = "en.json" + default_translation = load_translation(os.path.join(directory, default_file)) + translations = os.listdir(directory) + translations.remove(default_file) # Exclude default translation + + error_found = False + + for translation_file in translations: + other_file = os.path.join(directory, translation_file) + other_translation = load_translation(other_file) + + # Compare translations and get detailed error messages + errors = compare_translations(default_translation, other_translation, default_file, translation_file) + if errors: + error_found = True + print(f"File {translation_file} has missing translations for:") + for error in errors: + print(f" - {error}") + + if error_found: + sys.exit(1) # Exit with an error status code + else: + print("All translations are present") + sys.exit(0) + + +def main(): + """ + Parse command-line arguments, check for the existence of the specified directory, + and call check_translations with the provided or default directory. + + """ + parser = argparse.ArgumentParser( + description="Check and print missing translations for all non-default languages." + ) + parser.add_argument( + "--directory", + type=str, + nargs="?", + default=os.path.join(os.getcwd(), "lang"), + help="Directory containing translation files(relative to the root directory).", + ) + args = parser.parse_args() + + if not os.path.exists(args.directory): + print(f"Error: The specified directory '{args.directory}' does not exist.") + sys.exit(1) + + check_translations(args.directory) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 2099d2e9a0..f7e602e6d7 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -16,8 +16,8 @@ on: - 'master' env: - CODECOV_UNIQUE_NAME: CODECOV_UNIQUE_NAME-${{ github.run_id }}-${{ github.run_number }} - + CODECOV_UNIQUE_NAME: CODECOV_UNIQUE_NAME-${{ github.run_id }}-${{ github.run_number }} + jobs: Flutter-Codebase-Check: name: Checking codebase @@ -27,11 +27,11 @@ jobs: - uses: actions/checkout@v3 with: # ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 + fetch-depth: 0 - uses: actions/setup-java@v3 with: distribution: 'zulu' # See 'Supported distributions' for available options - java-version: '12.0' + java-version: '12.0' - uses: subosito/flutter-action@v2 with: flutter-version: '3.16.0' @@ -66,11 +66,16 @@ jobs: git checkout temp_branch pip install GitPython python ./.github/workflows/check_ignore.py --repository ${{github.repository}} --merge_branch_name ${{github.head_ref}} + - name: Compare translation files + run: | + chmod +x .github/workflows/compare_translations.py + python .github/workflows/compare_translations.py --directory lang + - name: Analysing codebase for default linting run: flutter analyze --no-pub - name: Analysing codebase for custom linting run: flutter pub run custom_lint - - name : Changed Files + - name: Changed Files id: changed-files uses: tj-actions/changed-files@v35 - name: List all changed files @@ -104,7 +109,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: 'zulu' # See 'Supported distributions' for available options - java-version: '12.0' + java-version: '12.0' - uses: subosito/flutter-action@v2 with: flutter-version: '3.16.0' @@ -118,13 +123,13 @@ jobs: with: verbose: true fail_ci_if_error: false - name: '${{env.CODECOV_UNIQUE_NAME}}' + name: '${{env.CODECOV_UNIQUE_NAME}}' - name: Test acceptable level of code coverage uses: VeryGoodOpenSource/very_good_coverage@v2 with: - path: './coverage/lcov.info' - min_coverage: 87.0 - + path: './coverage/lcov.info' + min_coverage: 90.0 + Android-Build: name: Testing build for android runs-on: ubuntu-latest @@ -134,7 +139,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: 'zulu' # See 'Supported distributions' for available options - java-version: '12.0' + java-version: '12.0' - uses: subosito/flutter-action@v2 with: flutter-version: '3.16.0' diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 4e1c64ab6d..2a4dd0ae1b 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -223,7 +223,7 @@ jobs: This may or may not be stable, so please have a look at the stable release(s). iOS-Build: - name: Testing build for iOS + name: iOS Build and Relaese runs-on: macos-latest needs: Flutter-Testing steps: @@ -235,3 +235,27 @@ jobs: architecture: x64 - name: Building for ios run: flutter build ios --release --no-codesign + # '--no-codesign' is used for building without code signing. + # For actual distribution, proper code signing is required. + + ######################################################## + ## Package the app as an .ipa and create a release ## + ######################################################## + + - name: Releasing for iOS + run: | + mkdir Payload + cp -r build/ios/iphoneos/Runner.app Payload/Runner.app + zip -r app.ipa Payload + # This packages the Runner.app into an .ipa file + + - uses: ncipollo/release-action@v1 + with: + name: "Automated iOS Release" + artifacts: "app-release.ipa" + allowUpdates: "true" + generateReleaseNotes: false + tag: "automated" + body: | + This is an automated release, triggered by a recent push. + This may or may not be stable, so please have a look at the stable release(s). diff --git a/.gitignore b/.gitignore index d0cd2ee334..b14badeec1 100644 --- a/.gitignore +++ b/.gitignore @@ -244,3 +244,12 @@ test/fixtures/core # Ignoring file that are generated during talawa testing and firebase initialization genhtml.perl test_img.png + + +# Ignoring Node files that are generated if user uses any node command which is not required for the project +node_modules/ +package.json +package-lock.json +yarn.lock +npm-debug.log +yarn-error.log \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c5696cb8a4..23a807c667 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,6 +100,7 @@ The process of proposing a change to Talawa can be summarized as: 1. Pull requests that don't meet the minimum test coverage levels will not be accepted. This may mean that you will have to create tests for code you did not write. You can decide which part of the code base needs additional tests if this happens to you. 1. **_Testing_:** 1. Test using the `flutter test` command. + 1. Review [Flutter's official introduction to unit testing](https://docs.flutter.dev/cookbook/testing/unit/introduction) 1. Here are some useful flutter test videos 1. [State Management With Provider](https://www.raywenderlich.com/6373413-state-management-with-provider) 1. [Unit Testing With Flutter: Getting Started](https://www.raywenderlich.com/6926998-unit-testing-with-flutter-getting-started) diff --git a/README.md b/README.md index 3e8d4e8f0c..c50e83c15a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Talawa -[💬 Join the community on Slack](https://join.slack.com/t/thepalisadoes-dyb6419/shared_invite/zt-28cswzsds-gfw~qPoxZOZv3vOYKokDKw) +[💬 Join the community on Slack](https://join.slack.com/t/thepalisadoes-dyb6419/shared_invite/zt-29oltereu-qJ931LcKxswuCAy29iA9WA) ![talawa-logo-lite-200x200](https://github.com/PalisadoesFoundation/talawa-admin/assets/16875803/26291ec5-d3c1-4135-8bc7-80885dff613d) diff --git a/lang/de.json b/lang/de.json index 7ff5a6eea1..a68bed5a8c 100644 --- a/lang/de.json +++ b/lang/de.json @@ -89,6 +89,16 @@ "Cancel": "Abbrechen", "Done": "Fertig", "Explore Events": "Veranstaltungen erkunden", + "Filters": "Filter", + "Filter by Date": "Filtern nach Datum", + "All Events": "Alle Veranstaltungen", + "Show all events": "Alle Veranstaltungen anzeigen", + "Show all events created by you": "Alle Veranstaltungen anzeigen, die von Ihnen erstellt wurden", + "Registered Events": "Registrierte Veranstaltungen", + "Show all events you have registered": "Alle Veranstaltungen anzeigen, für die Sie sich registriert haben", + "Show events for all": "Alle Veranstaltungen anzeigen", + "Show invite-only events": "Nur Einladungsveranstaltungen anzeigen", + "Add Date": "Datum hinzufügen", "Event": "Vorfall", "My Events": "Meine Veranstaltungen", @@ -152,7 +162,6 @@ "Logout": "Ausloggen", "Settings": "Einstellungen", "Dark Theme": "Dunkles Thema", - "Error": "Fout", "Warning": "Waarschuwing", "Information": "Informatie", @@ -162,5 +171,7 @@ "Notification Feature is not installed": "Meddelelsesfunktionen er ikke installeret", "For complete access, please": "Für vollständigen Zugriff bitte", " join an organization.": " einer Organisation beitreten.", - "JOIN":"BEITRETEN" + "JOIN":"BEITRETEN", + "Camera": "Kamera", + "Gallery": "Galerie" } diff --git a/lang/en.json b/lang/en.json index bed1b4a658..2588cffeb7 100644 --- a/lang/en.json +++ b/lang/en.json @@ -12,7 +12,6 @@ "Enter your password": "Enter your password", "Forgot password": "Forgot password", "Login": "Login", - "Notification Feature is not installed": "Notification Feature is not installed", "Sit back relax, we'll": "Sit back relax, we'll", "Recover": "Recover", @@ -35,7 +34,6 @@ "Next": "Next", "Request Sent to": "Request Sent to", "Log out": "Log out", - "Error": "Error", "Warning": "Warning", "Information": "Information", @@ -46,7 +44,6 @@ "Collaborate": "Collaborate", "with your": "with your", "Organizations": "Organizations", - "Title from the viewMode GSoC branch": "Title from the viewMode GSoC branch", "Please verify URL first": "Please verify URL first", "Enter a valid URL": "Enter a valid URL", @@ -61,20 +58,16 @@ "Password must not contain spaces": "Password must not contain spaces", "Password does not match original": "Password does not match original", "Join Organisation": "Join Organisation", - "We're": "We're", "Glad": "Glad", "you're": "you're", "Back": "Back", - "Let's": "Let's", "get": "get", "you": "you", "SignUp": "SignUp", - "Please wait": "Please wait", "for organisation(s) to accept your invitation.": "for organisation(s) to accept your invitation.", - "Add Event Title": "Add Event Title", "Where is the event": "Where is the event", "Add Location": "Add Location", @@ -102,6 +95,15 @@ "Cancel": "Cancel", "Done": "Done", "Explore Events": "Explore Events", + "Filters": "Filters", + "Filter by Date": "Filter by Date", + "All Events": "All Events", + "Show all events": "Show all events", + "Show all events created by you": "Show all events created by you", + "Registered Events": "Registered Events", + "Show all events you have registered": "Show all events you have registered", + "Show events for all": "Show events for all", + "Show invite-only events": "Show invite-only events", "Add Date": "Add Date", "Event": "Event", "My Events": "My Events", @@ -168,5 +170,7 @@ "No organizations found Please contact your admin": "No organizations found ! Please contact your admin", "For complete access, please": "For complete access, please", " join an organization.": " join an organization.", - "JOIN":"JOIN" + "JOIN":"JOIN", + "Camera": "Camera", + "Gallery": "Gallery" } diff --git a/lang/es.json b/lang/es.json index 5a8ffb18a3..6c6e705f3a 100644 --- a/lang/es.json +++ b/lang/es.json @@ -14,9 +14,7 @@ "Login": "Acceso", "Sit back relax, we'll": "Siéntese, ", "Recover": "", - "Notification Feature is not installed": "La función de notificación no está instalada", - "your password": "relájese, recuperaremos su contraseña", "Recover Password": "Recupera tu contraseña", "Select Language": "Seleccione el idioma", @@ -92,6 +90,15 @@ "Cancel": "Cancelar", "Done": "Hecho", "Explore Events": "Explorar eventos", + "Filters": "Filtros", + "Filter by Date": "Filtrar por fecha", + "All Events": "Todos los eventos", + "Show all events": "Mostrar todos los eventos", + "Show all events created by you": "Mostrar todos los eventos creados por ti", + "Registered Events": "Eventos registrados", + "Show all events you have registered": "Mostrar todos los eventos en los que te has registrado", + "Show events for all": "Mostrar eventos para todos", + "Show invite-only events": "Mostrar eventos solo por invitación", "Add Date": "Agregar fecha", "Event": "Evento", "My Events": "Mis Eventos", @@ -163,5 +170,7 @@ "No organizations found Please contact your admin": "Neniuj organizoj trovitaj! Bonvolu kontakti vian administranton", "For complete access, please": "Para acceso completo, por favor", " join an organization.": " unirse a una organización.", - "JOIN":"UNIRSE" + "JOIN":"UNIRSE", + "Camera": "Cámara", + "Gallery": "Galería" } diff --git a/lang/fr.json b/lang/fr.json index 5070b7deb7..3ed1ff2ab6 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -7,10 +7,8 @@ "Enter new password": "Entrez un nouveau mot de passe", "Re-Enter your password": "Entrez à nouveau votre mot de passe", "Change Password": "Changer le mot de passe", - "Email Hint": "test@test.org", "Notification Feature is not installed": "La fonction de notification n'est pas installée", - "Enter your registered Email": "Entrez votre email enregistré", "Enter your password": "Tapez votre mot de passe", "Forgot password": "Mot de passe oublié", @@ -36,7 +34,6 @@ "Next": "Suivante", "Request Sent to": "Demande envoyée à", "Log out": "Se déconnecter", - "Join": "Rejoignez", "and": "et", "Collaborate": "collaborez", @@ -61,7 +58,6 @@ "Password must not contain spaces": "Le mot de passe ne doit pas contenir d'espaces", "Password does not match original": "Le mot de passe ne correspond pas à l'original", "Join Organisation": "Rejoindre l'organisation", - "We're": "Nous sommes", "Glad": "heureux que vous", "you're": "soyez de", @@ -99,6 +95,15 @@ "Cancel": "Annuler", "Done": "Fait", "Explore Events": "Explorer les événements", + "Filters": "Filtres", + "Filter by Date": "Filtrer par date", + "All Events": "Tous les événements", + "Show all events": "Afficher tous les événements", + "Show all events created by you": "Afficher tous les événements créés par vous", + "Registered Events": "Événements enregistrés", + "Show all events you have registered": "Afficher tous les événements auxquels vous êtes inscrit", + "Show events for all": "Afficher les événements pour tous", + "Show invite-only events": "Afficher les événements sur invitation uniquement", "Add Date": "Ajouter une date", "Event": "Événement", "My Events": "Mes événements", @@ -165,5 +170,7 @@ "No organizations found Please contact your admin": "Aucune organisation trouvée ! Veuillez contacter votre administrateur", "For complete access, please": "Pour un accès complet, veuillez", " join an organization.": " rejoindre une organisation.", - "JOIN":"REJOINDRE" + "JOIN":"REJOINDRE", + "Camera": "Caméra", + "Gallery": "Galerie" } diff --git a/lang/hi.json b/lang/hi.json index db33287420..8ae54f08e5 100644 --- a/lang/hi.json +++ b/lang/hi.json @@ -90,6 +90,15 @@ "Cancel": "रद्द करना", "Done": "किया हुआ", "Explore Events": "घटनाओं का अन्वेषण करें", + "Filters": "फ़िल्टर", + "Filter by Date": "तारीख़ से फ़िल्टर करें", + "All Events": "सभी घटनाएँ", + "Show all events": "सभी घटनाएँ दिखाएं", + "Show all events created by you": "आपके द्वारा बनाई गई सभी घटनाएँ दिखाएं", + "Registered Events": "रजिस्टर की गई घटनाएँ", + "Show all events you have registered": "आपने जिन घटनाओं में पंजीकृत हुए हैं, उन सभी घटनाएँ दिखाएं", + "Show events for all": "सभी के लिए घटनाएँ दिखाएं", + "Show invite-only events": "आमंत्रित लोगों के लिए घटनाएँ दिखाएं", "Add Date": "तिथि जोड़ें", "Event": "प्रतिस्पर्धा", "My Events": "मेरे कार्यक्रम", @@ -161,5 +170,7 @@ "No organizations found Please contact your admin": "कोई संगठन नहीं मिला! कृपया अपने व्यवस्थापक से संपर्क करें", "For complete access, please": "पूर्ण पहुंच के लिए, कृपया", " join an organization.": " किसी संगठन से जुड़ें.", - "JOIN":"जोड़ना" + "JOIN":"जोड़ना", + "Camera": "कैमरा", + "Gallery": "गैलरी" } diff --git a/lang/ja.json b/lang/ja.json index cf17112a0d..6a57e49f7c 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -14,7 +14,6 @@ "Login": "ログイン", "Sit back relax, we'll": "ゆったりとおくつろぎください。", "Recover": "回復", - "Notification Feature is not installed": "通知機能がインストールされていません", "your password": "あなたのパスワード", "Recover Password": "パスワードを回復", @@ -62,10 +61,8 @@ "get": "得る", "you": "あなた", "SignUp": "サインアップ", - "Please wait": "お待ちください", "for organisation(s) to accept your invitation.": "組織があなたの招待を受け入れるために。", - "Add Event Title": "イベントタイトルを追加", "Where is the event": "イベントはどこですか", "Add Location": "場所を追加", @@ -93,6 +90,15 @@ "Cancel": "キャンセル", "Done": "終わり", "Explore Events": "イベントを探索する", + "Filters": "フィルター", + "Filter by Date": "日付で絞り込む", + "All Events": "すべてのイベント", + "Show all events": "すべてのイベントを表示", + "Show all events created by you": "あなたが作成したすべてのイベントを表示", + "Registered Events": "登録済みイベント", + "Show all events you have registered": "あなたが登録したすべてのイベントを表示", + "Show events for all": "すべてのイベントを表示", + "Show invite-only events": "招待限定イベントを表示", "Add Date": "日付を追加", "Event": "イベント", "My Events": "私のイベント", @@ -164,5 +170,7 @@ "No organizations found Please contact your admin": "組織が見つかりません!管理者に連絡してください", "For complete access, please": "完全にアクセスするには、", " join an organization.": " 組織に参加します。", - "JOIN": "参加する" + "JOIN": "参加する", + "Camera": "カメラ", + "Gallery": "ギャラリー" } diff --git a/lang/pt.json b/lang/pt.json index 6948f0e918..1b156b3b68 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -13,9 +13,7 @@ "Forgot password": "Esqueceu sua senha", "Login": "Conecte-se", "Sit back relax, we'll": "Sente-se relaxe, vamos", - "Notification Feature is not installed": "O recurso de notificação não está instalado", - "Recover": "Recuperar", "your password": "sua senha", "Recover Password": "Recuperar senha", @@ -92,6 +90,15 @@ "Cancel": "Cancelar", "Done": "Feita", "Explore Events": "Explorar eventos", + "Filters": "Filtros", + "Filter by Date": "Filtrar por data", + "All Events": "Todos os eventos", + "Show all events": "Mostrar todos os eventos", + "Show all events created by you": "Mostrar todos os eventos criados por você", + "Registered Events": "Eventos registrados", + "Show all events you have registered": "Mostrar todos os eventos em que você se registrou", + "Show events for all": "Mostrar eventos para todos", + "Show invite-only events": "Mostrar apenas eventos com convite", "Add Date": "Adicionar Data", "Event": "Evento", "My Events": "Meus Eventos", @@ -163,5 +170,7 @@ "No organizations found Please contact your admin": "Neniuj organizoj trovitaj! Bonvolu kontakti vian administranton", "For complete access, please": "Para acesso completo, por favor", " join an organization.": " ingressar em uma organização.", - "JOIN":"ENTRAR" + "JOIN":"ENTRAR", + "Camera": "Câmera", + "Gallery": "Galeria" } diff --git a/lang/zh.json b/lang/zh.json index 8d96d47899..42d7157e05 100644 --- a/lang/zh.json +++ b/lang/zh.json @@ -15,7 +15,6 @@ "Sit back relax, we'll": "高枕无忧, ", "Recover": "", "Notification Feature is not installed": "未安装通知功能\n", - "your password": "我们会找回您的密码", "Recover Password": "恢复你的密码", "Select Language": "选择语言", @@ -91,11 +90,20 @@ "Cancel": "取消", "Done": "完成", "Explore Events": "探索事件", - "Add Date": "添加日期", - "Event": "事件", + "Filters": "过滤器", + "Filter by Date": "按日期过滤", + "All Events": "所有活动", + "Show all events": "显示所有活动", "My Events": "我的活动", - "Public Events": "公共事件", + "Show all events created by you": "显示由您创建的所有活动", + "Registered Events": "已注册活动", + "Show all events you have registered": "显示您已注册的所有活动", + "Public Events": "公共活动", + "Show events for all": "显示所有人的活动", "Private Events": "私人活动", + "Show invite-only events": "仅显示邀请活动", + "Add Date": "添加日期", + "Event": "事件", "Liked by": "喜欢的人", "Comments": "评论", "FirstName LastName": "名字姓氏", @@ -162,5 +170,7 @@ "No organizations found Please contact your admin": "Neniuj organizoj trovitaj! Bonvolu kontakti vian administranton", "For complete access, please": "如需完整访问,请", " join an organization.": " 加入一个组织。", - "JOIN":"加入" + "JOIN":"加入", + "Camera": "相机", + "Gallery": "画廊" } diff --git a/lib/locator.dart b/lib/locator.dart index a980f45f3b..0c7dd4f3d7 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -8,6 +8,7 @@ import 'package:talawa/services/comment_service.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/services/event_service.dart'; import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/org_service.dart'; import 'package:talawa/services/post_service.dart'; @@ -70,11 +71,8 @@ final connectivity = locator(); ///creating GetIt for OrganizationService. final organizationService = locator(); -///creating GetIt for ImageCropper. -final imageCropper = locator(); - -///creating GetIt for ImagePicker. -final imagePicker = locator(); +///creating GetIt for ImageService. +final imageService = locator(); /// This function registers the widgets/objects in "GetIt". /// @@ -104,8 +102,9 @@ void setupLocator() { locator.registerLazySingleton(() => MultiMediaPickerService()); locator.registerLazySingleton(() => Connectivity()); locator.registerLazySingleton(() => ChatService()); - locator.registerLazySingleton(() => ImageCropper()); + locator.registerLazySingleton(() => ImageService()); locator.registerLazySingleton(() => ImagePicker()); + locator.registerLazySingleton(() => ImageCropper()); //graphql locator.registerSingleton(GraphqlConfig()); diff --git a/lib/services/comment_service.dart b/lib/services/comment_service.dart index 655a2b476e..dc457e5048 100644 --- a/lib/services/comment_service.dart +++ b/lib/services/comment_service.dart @@ -1,9 +1,8 @@ -// ignore_for_file: talawa_api_doc, avoid_dynamic_calls -// ignore_for_file: talawa_good_doc_comments - +import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:talawa/locator.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/utils/comment_queries.dart'; +import 'package:talawa/utils/post_queries.dart'; /// CommentService class have different member functions which provides service in the context of commenting. /// @@ -18,11 +17,14 @@ class CommentService { /// This function is used to add comment on the post. /// - /// parameters: - /// * [postId] - Post id where comment need to be added. - /// * [text] - content of the comment. + /// To verify things are working, check out the native platform logs. + /// **params**: + /// * `postId`: The post id on which comment is to be added. + /// * `text`: The comment text. + /// + /// **returns**: + /// * `Future`: promise that will be fulfilled message background activities are successful. Future createComments(String postId, String text) async { - print("comment service called"); final String createCommentQuery = CommentQueries().createComment(); final result = await _dbFunctions.gqlAuthMutation( createCommentQuery, @@ -31,21 +33,30 @@ class CommentService { 'text': text, }, ); - print("comment added"); - print(result); return result; } - /// This function is used to fetch all comments on the post. + /// This function is used to get all comments on the post. + /// + /// To verify things are working, check out the native platform logs. + /// **params**: + /// * `postId`: The post id for which comments are to be fetched. + /// + /// **returns**: + /// * `Future>`: promise that will be fulfilled with list of comments. /// - /// parameters: - /// * [postId] - Post id for which comments need to be fetched. - Future getCommentsForPost(String postId) async { - final String getCommmentQuery = CommentQueries().getPostsComments(postId); - final result = await _dbFunctions.gqlAuthMutation(getCommmentQuery); - if (result.data != null) { - return result.data["commentsByPost"] as List; + Future> getCommentsForPost(String postId) async { + final String getCommmentQuery = PostQueries().getPostById(postId); + + final dynamic result = await _dbFunctions.gqlAuthMutation(getCommmentQuery); + + if (result == null) { + return []; } - return []; + final resultData = (result as QueryResult).data; + + final resultDataPostComments = (resultData?['post'] + as Map)['comments'] as List; + return resultDataPostComments; } } diff --git a/lib/services/database_mutation_functions.dart b/lib/services/database_mutation_functions.dart index 6f420e1199..d9aeec90e4 100644 --- a/lib/services/database_mutation_functions.dart +++ b/lib/services/database_mutation_functions.dart @@ -381,10 +381,10 @@ class DataBaseMutationFunctions { fetchOrgById(id); } } else if (result.data != null && result.isConcrete) { + print(result.data!['organizations']); return OrgInfo.fromJson( // ignore: collection_methods_unrelated_type - (result.data!['organizations'] as Map)[0] - as Map, + (result.data!['organizations'] as List>)[0], ); } return false; diff --git a/lib/services/image_service.dart b/lib/services/image_service.dart new file mode 100644 index 0000000000..3d57e3ebfb --- /dev/null +++ b/lib/services/image_service.dart @@ -0,0 +1,80 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:talawa/locator.dart'; + +/// ImageService class provides different functions as service in the context of Images. +/// +/// Services include: +/// * `cropImage` +/// * `convertToBase64` +class ImageService { + /// Global instance of ImageCropper. + final ImageCropper _imageCropper = locator(); + + /// Crops the image selected by the user. + /// + /// **params**: + /// * `imageFile`: the image file to be cropped. + /// + /// **returns**: + /// * `Future`: the image after been cropped. + /// + /// **throws**: + /// - `Exception`: If an error occurs during the image cropping process. + Future cropImage({required File imageFile}) async { + // try, to crop the image and returns a File with cropped image path. + try { + final CroppedFile? croppedImage = await _imageCropper.cropImage( + sourcePath: imageFile.path, + aspectRatioPresets: [ + CropAspectRatioPreset.square, + CropAspectRatioPreset.original, + ], + uiSettings: [ + AndroidUiSettings( + toolbarTitle: 'Crop Image', + toolbarColor: const Color(0xff18191A), + toolbarWidgetColor: Colors.white, + backgroundColor: Colors.black, + cropGridColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false, + ), + IOSUiSettings( + minimumAspectRatio: 1.0, + ), + ], + ); + + if (croppedImage != null) { + return File(croppedImage.path); + } + } catch (e) { + throw Exception( + "ImageService : $e.", + ); + } + + return null; + } + + /// Converts the image into Base64 format. + /// + /// **params**: + /// * `file`: Image as a File object. + /// + /// **returns**: + /// * `Future`: image in string format + Future convertToBase64(File file) async { + try { + final List bytes = await file.readAsBytes(); + final String base64String = base64Encode(bytes); + return base64String; + } catch (error) { + return null; + } + } +} diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index ae9260dbad..805bcc7680 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -16,6 +16,7 @@ import 'package:talawa/widgets/talawa_error_snackbar.dart'; /// * `showTalawaErrorDialog` /// * `pop` class NavigationService { + /// Key for Navigator State. GlobalKey navigatorKey = GlobalKey(); /// Pushes a Screen. @@ -106,6 +107,13 @@ class NavigationService { } /// This is used for the quick alert of `duration: 2 seconds` with text message(passed). + /// + /// **params**: + /// * `message`: Message would be shown on snackbar + /// * `duration`: Duration of Snackbar + /// + /// **returns**: + /// None void showSnackBar( String message, { Duration duration = const Duration(seconds: 2), @@ -119,6 +127,15 @@ class NavigationService { ); } + /// This is used for the quick error of `duration: 2 seconds`. + /// + /// **params**: + /// * `errorMessage`: Error Message shown in snackbar + /// * `messageType`: Type of Message + /// * `duration`: Duration of snackbar + /// + /// **returns**: + /// None void showTalawaErrorSnackBar( String errorMessage, MessageType messageType, @@ -138,6 +155,14 @@ class NavigationService { ); } + /// Shows an Error Dialog Box. + /// + /// **params**: + /// * `errorMessage`: Message shown in dialog + /// * `messageType`: Type of Message + /// + /// **returns**: + /// None void showTalawaErrorDialog(String errorMessage, MessageType messageType) { showDialog( context: navigatorKey.currentContext!, @@ -153,6 +178,12 @@ class NavigationService { } /// This function pops the current state. + /// + /// **params**: + /// None + /// + /// **returns**: + /// None void pop() { return navigatorKey.currentState!.pop(); } diff --git a/lib/services/third_party_service/multi_media_pick_service.dart b/lib/services/third_party_service/multi_media_pick_service.dart index b793f4b2dc..616c0dc31c 100644 --- a/lib/services/third_party_service/multi_media_pick_service.dart +++ b/lib/services/third_party_service/multi_media_pick_service.dart @@ -6,12 +6,12 @@ Service usage: "add_post_view_model.dart" import 'dart:async'; import 'dart:io'; -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:talawa/locator.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/widgets/custom_alert_dialog.dart'; @@ -26,15 +26,22 @@ class MultiMediaPickerService { MultiMediaPickerService() { _picker = locator(); _fileStream = _fileStreamController.stream.asBroadcastStream(); + _imageService = imageService; } - //Local Variables + /// Controller for handling the stream of selected files. final StreamController _fileStreamController = StreamController(); + + /// Stream of selected files. late Stream _fileStream; + + /// [ImagePicker] used for selecting images or videos. late ImagePicker _picker; - //Getters - /// This function returns the stream of files. + /// [ImageService] for additional image-related operations. + late ImageService _imageService; + + /// Provides a stream of selected multimedia files. /// /// params: /// None. @@ -43,11 +50,10 @@ class MultiMediaPickerService { /// * `Stream`: Stream of files. Stream get fileStream => _fileStream; - /// This function is used to pick the image from gallery or to click the image from user's camera. - /// - /// The function first ask for the permission to access the camera, if denied then returns a message in. + /// Picks the image from gallery or to click the image from user's camera. /// - /// custom Dialog Box. This function returns a File type for which `camera` variable is false by default. + /// First ask for the permission to access the camera, if denied then returns a message in. + /// custom Dialog Box. Returns a File type for which `camera` variable is false by default. /// /// **params**: /// * `camera`: if true then open camera for image, else open gallery to select image. @@ -63,73 +69,46 @@ class MultiMediaPickerService { ); // if image is selected or not null, call the cropImage function that provide service to crop the selected image. if (image != null) { - return await cropImage(imageFile: File(image.path)); + return await _imageService.cropImage( + imageFile: File(image.path), + ); } } catch (e) { // if the permission denied or error occurs. if (e is PlatformException && e.code == 'camera_access_denied') { // push the dialog alert with the message. locator().pushDialog( - CustomAlertDialog( - success: () { - locator().pop(); - openAppSettings(); - }, - dialogTitle: 'Permission Denied', - successText: 'SETTINGS', - dialogSubTitle: - "Camera permission is required, to use this feature, give permission from app settings", - ), + permissionDeniedDialog(), ); } - print( + debugPrint( "MultiMediaPickerService : Exception occurred while choosing photo from the gallery $e", ); } + return null; } - /// This function is used to crop the image selected by the user. + /// Generates a custom alert dialog for permission denial. /// - /// The function accepts a `File` type image and returns `File` type of cropped image. + /// When called, it creates and returns a `CustomAlertDialog` widget with pre-defined settings. + /// This dialog prompts the user to grant camera permissions from the app settings. /// /// **params**: - /// * `imageFile`: the image file to be cropped. + /// None /// /// **returns**: - /// * `Future`: the image after been cropped. - Future cropImage({required File imageFile}) async { - // try, to crop the image and returns a File with cropped image path. - try { - final CroppedFile? croppedImage = await locator().cropImage( - sourcePath: imageFile.path, - aspectRatioPresets: [ - CropAspectRatioPreset.square, - CropAspectRatioPreset.original, - ], - uiSettings: [ - AndroidUiSettings( - toolbarTitle: 'Crop Image', - toolbarColor: const Color(0xff18191A), - toolbarWidgetColor: Colors.white, - backgroundColor: Colors.black, - cropGridColor: Colors.white, - initAspectRatio: CropAspectRatioPreset.original, - lockAspectRatio: false, - ), - IOSUiSettings( - minimumAspectRatio: 1.0, - ), - ], - ); - if (croppedImage != null) { - return File(croppedImage.path); - } - } catch (e) { - print( - "MultiMediaPickerService : Exception occurred while cropping Image", - ); - } - return null; + /// * `CustomAlertDialog`: Custom Alert Dialog widget. + CustomAlertDialog permissionDeniedDialog() { + return CustomAlertDialog( + success: () { + locator().pop(); + openAppSettings(); + }, + dialogTitle: 'Permission Denied', + successText: 'SETTINGS', + dialogSubTitle: + "Camera permission is required, to use this feature, give permission from app settings", + ); } } diff --git a/lib/services/user_config.dart b/lib/services/user_config.dart index 73fcfa739d..62683ad023 100644 --- a/lib/services/user_config.dart +++ b/lib/services/user_config.dart @@ -84,6 +84,7 @@ class UserConfig { _currentOrgInfoController.add(_currentOrg!); _currentUser = boxUser.get('user'); + // if there is not currentUser then returns false. if (_currentUser == null) { _currentUser = User(id: 'null', authToken: 'null'); @@ -108,6 +109,7 @@ class UserConfig { _currentOrgInfoController.add(_currentOrg!); saveUserInHive(); + return true; } on Exception catch (e) { print(e); diff --git a/lib/utils/post_queries.dart b/lib/utils/post_queries.dart index c5b73d922f..40cff4d0d6 100644 --- a/lib/utils/post_queries.dart +++ b/lib/utils/post_queries.dart @@ -40,6 +40,52 @@ class PostQueries { """; } + /// Getting Post by Post Id. + /// + /// **params**: + /// * `postId`: The post id + /// + /// **returns**: + /// * `String`: The query related to gettingPostsbyId + String getPostById(String postId) { + return """ + query { + post(id: "$postId") + { + _id + text + createdAt + imageUrl + videoUrl + title + commentCount + likeCount + creator{ + _id + firstName + lastName + image + } + organization{ + _id + } + likedBy{ + _id + } + comments{ + _id, + text, + createdAt + creator{ + firstName + lastName + } + } + } + } +"""; + } + /// Add Like to a post. /// /// **params**: diff --git a/lib/view_model/access_request_view_model.dart b/lib/view_model/access_request_view_model.dart index a2e8648ab9..1d539c065d 100644 --- a/lib/view_model/access_request_view_model.dart +++ b/lib/view_model/access_request_view_model.dart @@ -1,4 +1,3 @@ -// ignore_for_file: talawa_api_doc import 'package:flutter/cupertino.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:talawa/constants/routing_constants.dart'; @@ -23,7 +22,6 @@ class AccessScreenViewModel extends BaseModel { /// initialization function. /// - /// /// **params**: /// * `org`: Org to send request to. /// diff --git a/lib/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart b/lib/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart index 0bcc7ea1ad..af53cb5dda 100644 --- a/lib/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart +++ b/lib/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -7,6 +6,7 @@ import 'package:talawa/enums/enums.dart'; import 'package:talawa/locator.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/services/database_mutation_functions.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; import 'package:talawa/services/user_config.dart'; @@ -22,6 +22,7 @@ class AddPostViewModel extends BaseModel { //Services late MultiMediaPickerService _multiMediaPickerService; late NavigationService _navigationService; + late ImageService _imageService; // ignore: unused_field late File? _imageFile; @@ -65,7 +66,7 @@ class AddPostViewModel extends BaseModel { /// **returns**: /// * `Future`: define_the_return Future setImageInBase64(File file) async { - _imageInBase64 = await convertToBase64(file); + _imageInBase64 = await _imageService.convertToBase64(file); notifyListeners(); } @@ -78,6 +79,9 @@ class AddPostViewModel extends BaseModel { String get userName => userConfig.currentUser.firstName! + userConfig.currentUser.lastName!; + /// User profile picture. + String? get userPic => userConfig.currentUser.image; + /// The organisation name. /// /// params: @@ -121,33 +125,15 @@ class AddPostViewModel extends BaseModel { void initialise() { _navigationService = locator(); _imageFile = null; + _imageInBase64 = null; _multiMediaPickerService = locator(); + _imageService = locator(); if (!demoMode) { _dbFunctions = locator(); _selectedOrg = locator().currentOrg; } } - /// to convert the image in base64. - /// - /// - /// **params**: - /// * `file`: file of image clicked. - /// - /// **returns**: - /// * `Future`: Future string containing the base 64 format image - Future convertToBase64(File file) async { - try { - final List bytes = await file.readAsBytes(); - final String base64String = base64Encode(bytes); - print(base64String); - _imageInBase64 = base64String; - return base64String; - } catch (error) { - return ''; - } - } - /// This function is used to get the image from gallery. /// /// The function uses the `_multiMediaPickerService` services. @@ -164,7 +150,7 @@ class AddPostViewModel extends BaseModel { if (image != null) { _imageFile = image; // convertImageToBase64(image.path); - convertToBase64(image); + _imageInBase64 = await _imageService.convertToBase64(image); // print(_imageInBase64); _navigationService.showTalawaErrorSnackBar( "Image is added", diff --git a/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart b/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart index be1a52fdb3..20e83f6f03 100644 --- a/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart +++ b/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart @@ -1,8 +1,10 @@ import 'dart:convert'; import 'dart:io'; - import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:talawa/enums/enums.dart'; import 'package:talawa/locator.dart'; +import 'package:talawa/models/user/user_info.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; import 'package:talawa/view_model/base_view_model.dart'; @@ -18,6 +20,9 @@ class EditProfilePageViewModel extends BaseModel { /// profile image. late File? imageFile; + /// profile image in base64. + String? base64Image; + /// first name controller. TextEditingController firstNameTextController = TextEditingController(); @@ -54,7 +59,7 @@ class EditProfilePageViewModel extends BaseModel { /// /// **returns**: /// * `Future`: None - Future getImageFromGallery({bool camera = false}) async { + Future getImage({bool camera = false}) async { final image = await _multiMediaPickerService.getPhotoFromGallery(camera: camera); if (image != null) { @@ -63,6 +68,21 @@ class EditProfilePageViewModel extends BaseModel { } } + /// Method to select image from gallery or camera. + /// + /// **params**: + /// * `camera`: for true it will select from camera otherwise gallery + /// + /// **returns**: + /// * `Future`: none + Future selectImage({bool camera = false}) async { + if (camera) { + getImage(camera: true); + } else { + getImage(); + } + } + /// This function is used to convert the image into Base64 format. /// /// **params**: @@ -73,15 +93,83 @@ class EditProfilePageViewModel extends BaseModel { Future convertToBase64(File file) async { try { final List bytes = await file.readAsBytes(); - final String base64String = base64Encode(bytes); - print(base64String); - imageFile = base64String as File?; - return base64String; + base64Image = base64Encode(bytes); + return base64Image!; } catch (error) { return ''; } } + /// Method to update user profile. + /// + /// **params**: + /// * `firstName`: updated first name. + /// * `lastName`: updated last name. + /// * `newImage`: New profile picture that is to be updated. + /// + /// **returns**: + /// * `Future`: none + Future updateUserProfile({ + String? firstName, + String? lastName, + File? newImage, + }) async { + if (firstName == user.firstName && + newImage == null && + lastName == user.lastName) { + return; + } + try { + final Map variables = {}; + if (firstName != null) { + variables["firstName"] = firstName; + } + if (lastName != null) { + variables["lastName"] = lastName; + } + if (newImage != null) { + final String imageAsString = await convertToBase64(newImage); + variables["file"] = 'data:image/png;base64,$imageAsString'; + } + if (variables.isNotEmpty) { + await databaseService.gqlAuthMutation( + queries.updateUserProfile(), + variables: variables, + ); + // Fetch updated user info from the database and save it in hivebox. + final QueryResult result1 = await databaseFunctions.gqlAuthQuery( + queries.fetchUserInfo, + variables: {'id': user.id}, + ) as QueryResult; + final User userInfo = User.fromJson( + ((result1.data!['users'] as List)[0]) + as Map, + fromOrg: true, + ); + userInfo.authToken = userConfig.currentUser.authToken; + userInfo.refreshToken = userConfig.currentUser.refreshToken; + userConfig.updateUser(userInfo); + notifyListeners(); + + user.firstName = firstName ?? user.firstName; + user.lastName = lastName ?? user.lastName; + firstNameTextController.text = user.firstName!; + lastNameTextController.text = user.lastName!; + + navigationService.showTalawaErrorSnackBar( + "Profile updated successfully", + MessageType.info, + ); + notifyListeners(); + } + } on Exception catch (_) { + navigationService.showTalawaErrorSnackBar( + "Something went wrong", + MessageType.error, + ); + } + } + /// This function remove the selected image. /// /// **params**: diff --git a/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart b/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart index da974fede1..5df3b5678c 100644 --- a/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart +++ b/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart @@ -289,7 +289,7 @@ class ProfilePageViewModel extends BaseModel { void Function(void Function()) setter, ) { return InkWell( - key: const Key('dombtn1'), + key: Key('domBtn_$amount'), onTap: () { setter(() { donationAmount.text = amount; diff --git a/lib/view_model/pre_auth_view_models/select_organization_view_model.dart b/lib/view_model/pre_auth_view_models/select_organization_view_model.dart index 170da8b868..688b6878a0 100644 --- a/lib/view_model/pre_auth_view_models/select_organization_view_model.dart +++ b/lib/view_model/pre_auth_view_models/select_organization_view_model.dart @@ -259,9 +259,9 @@ class SelectOrganizationViewModel extends BaseModel { return { 'organizationsConnection': (existingOrganizations!["organizationsConnection"] - as List>) + + as List) + (newOrganizations!['organizationsConnection'] - as List>), + as List), }; }, ), diff --git a/lib/view_model/widgets_view_models/comments_view_model.dart b/lib/view_model/widgets_view_models/comments_view_model.dart index f189e528ae..4bc9b31ef0 100644 --- a/lib/view_model/widgets_view_models/comments_view_model.dart +++ b/lib/view_model/widgets_view_models/comments_view_model.dart @@ -1,6 +1,3 @@ -// ignore_for_file: talawa_api_doc -// ignore_for_file: talawa_good_doc_comments - import 'package:talawa/enums/enums.dart'; import 'package:talawa/locator.dart'; import 'package:talawa/models/comment/comment_model.dart'; @@ -9,25 +6,41 @@ import 'package:talawa/services/post_service.dart'; import 'package:talawa/services/user_config.dart'; import 'package:talawa/view_model/base_view_model.dart'; -/// CommentsViewModel class helps to serve the data from model -/// and to react to user's input for Comment Widget. +/// CommentsViewModel class helps to serve the data from model and to react to user's input for Comment Widget. /// /// Methods include: /// * `getComments` : to get all comments on the post. /// * `createComment` : to add comment on the post. class CommentsViewModel extends BaseModel { + /// Constructor late CommentService _commentService; + + /// PostService instance. late PostService _postService; + + /// Post id on which comments are to be fetched. late String _postID; + + /// List of comments on the post. late List _commentlist; + + /// UserConfig instance. late UserConfig _userConfig; // Getters List get commentList => _commentlist; String get postId => _postID; - // initialiser. - Future initialise(String postID) async { + /// This function is used to initialise the CommentViewModel. + /// + /// To verify things are working, check out the native platform logs. + /// **params**: + /// * `postID`: The post id for which comments are to be fetched. + /// + /// **returns**: + /// * `Future`: promise that will be fulfilled message background activities are successful. + /// + Future initialise(String postID) async { _commentlist = []; _postID = postID; _commentService = locator(); @@ -37,12 +50,18 @@ class CommentsViewModel extends BaseModel { await getComments(); } - /// This methods fetch all comments on the post. - /// The function uses `getCommentsForPost` method by Comment Service. - Future getComments() async { + /// This function is used to get all comments on the post. + /// + /// To verify things are working, check out the native platform logs. + /// **params**: + /// None + /// + /// **returns**: + /// * `Future`: promise that will be fulfilled when comments are fetched. + /// + Future getComments() async { setState(ViewState.busy); - final List commentsJSON = - await _commentService.getCommentsForPost(_postID) as List; + final List commentsJSON = await _commentService.getCommentsForPost(_postID); print(commentsJSON); commentsJSON.forEach((commentJson) { _commentlist.add(Comment.fromJson(commentJson as Map)); @@ -50,18 +69,27 @@ class CommentsViewModel extends BaseModel { setState(ViewState.idle); } - /// This function add comment on the post. - /// The function uses `createComments` method provided by Comment Service. + /// This function add comment on the post. The function uses `createComments` method provided by Comment Service. + /// + /// **params**: + /// * `msg`: The comment text. /// - /// params: - /// * `msg` : text of the comment to add. - Future createComment(String msg) async { + /// **returns**: + /// * `Future`: promise that will be fulfilled when comment is added. + /// + Future createComment(String msg) async { print("comment viewModel called"); await _commentService.createComments(_postID, msg); addCommentLocally(msg); } - // This function add comment locally. + /// This function add comment locally. + /// + /// **params**: + /// * `msg`: BuildContext, contain parent info + /// + /// **returns**: + /// None void addCommentLocally(String msg) { _postService.addCommentLocally(_postID); final creator = _userConfig.currentUser; @@ -70,7 +98,7 @@ class CommentsViewModel extends BaseModel { createdAt: DateTime.now().toString(), creator: creator, ); - _commentlist.insert(0, localComment); + _commentlist.add(localComment); notifyListeners(); } } diff --git a/lib/view_model/widgets_view_models/custom_drawer_view_model.dart b/lib/view_model/widgets_view_models/custom_drawer_view_model.dart index 0abcb07420..1daab37b25 100644 --- a/lib/view_model/widgets_view_models/custom_drawer_view_model.dart +++ b/lib/view_model/widgets_view_models/custom_drawer_view_model.dart @@ -75,7 +75,8 @@ class CustomDrawerViewModel extends BaseModel { /// None void switchOrg(OrgInfo switchToOrg) { // if `selectedOrg` is equal to `switchOrg` and `switchToOrg` present or not. - if (selectedOrg == switchToOrg && isPresentinSwitchableOrg(switchToOrg)) { + if ((selectedOrg == switchToOrg) && + (isPresentinSwitchableOrg(switchToOrg))) { // _navigationService.pop(); navigationService.showTalawaErrorSnackBar( '${switchToOrg.name} already selected', diff --git a/lib/view_model/widgets_view_models/like_button_view_model.dart b/lib/view_model/widgets_view_models/like_button_view_model.dart index f2ff2ce9fa..ee63f1e105 100644 --- a/lib/view_model/widgets_view_models/like_button_view_model.dart +++ b/lib/view_model/widgets_view_models/like_button_view_model.dart @@ -28,7 +28,7 @@ class LikeButtonViewModel extends BaseModel { // ignore: unused_field late StreamSubscription _updatePostSubscription; - // Getters + ///Getters. bool get isLiked => _isLiked; List get likedBy => _likedBy; int get likesCount => _likedBy.length; diff --git a/lib/views/after_auth_screens/add_post_page.dart b/lib/views/after_auth_screens/add_post_page.dart index 7ed9fba9e8..028f751a9d 100644 --- a/lib/views/after_auth_screens/add_post_page.dart +++ b/lib/views/after_auth_screens/add_post_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:talawa/enums/enums.dart'; import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart'; import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_avatar.dart'; /// Add Post View Model. late AddPostViewModel model; @@ -81,7 +83,12 @@ class _AddPostState extends State { child: Column( children: [ ListTile( - leading: const CircleAvatar(radius: 25), + leading: CustomAvatar( + isImageNull: model.userPic == null, + firstAlphabet: model.userName.substring(0, 1).toUpperCase(), + imageUrl: model.userPic, + fontSize: SizeConfig.screenHeight! * 0.018, + ), title: Text(model.userName), subtitle: Text( AppLocalizations.of(context)! diff --git a/lib/views/after_auth_screens/events/event_filter_bottomsheet.dart b/lib/views/after_auth_screens/events/event_filter_bottomsheet.dart new file mode 100644 index 0000000000..a164521485 --- /dev/null +++ b/lib/views/after_auth_screens/events/event_filter_bottomsheet.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/apptheme.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; + +/// Shows a list of dropdown taken from `model` and `context`. +/// +/// **params**: +/// * `model`: contains the events data +/// * `context`: the overall context of UI +/// +/// **returns**: +/// * `Widget`: the dropdown +Widget dropDownList( + ExploreEventsViewModel model, + BuildContext context, +) { + final Map filters = { + 'All Events': 'Show all events', + 'My Events': 'Show all events created by you', + 'Registered Events': 'Show all events you have registered', + 'Public Events': 'Show events for all', + 'Private Events': 'Show invite-only events', + }; + return SizedBox( + height: SizeConfig.screenHeight, + width: SizeConfig.screenWidth, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: SizeConfig.safeBlockHorizontal! * 10, + ), + child: StatefulBuilder( + builder: (_, StateSetter setState) { + return SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: SizeConfig.safeBlockVertical, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of( + context, + )! + .strictTranslate( + "Filters", + ), + style: Theme.of( + context, + ).textTheme.headlineSmall, + ), + IconButton( + key: const Key('close'), + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon( + Icons.close, + ), + ), + ], + ), + ...List.generate( + filters.length, + (index) { + return Padding( + padding: EdgeInsets.symmetric( + vertical: SizeConfig.safeBlockVertical! * 2, + ), + child: GestureDetector( + onTap: () { + model.choseValueFromDropdown( + filters.keys.toList()[index], + ); + setState(() {}); + }, + child: Container( + key: Key( + filters.keys.toList()[index], + ), + decoration: BoxDecoration( + color: model.chosenValue == + filters.keys.toList()[index] + ? Theme.of(context).colorScheme.secondary + : AppTheme.white, + borderRadius: BorderRadius.all( + Radius.circular( + SizeConfig.safeBlockHorizontal! * 2, + ), + ), + ), + width: SizeConfig.screenWidth! - 60, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: SizeConfig.safeBlockHorizontal! * 5, + vertical: SizeConfig.safeBlockVertical! / 2, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.strictTranslate( + filters.keys.toList()[index], + ), + style: Theme.of(context) + .textTheme + .labelLarge! + .copyWith( + color: model.chosenValue == + filters.keys.toList()[index] + ? AppTheme.white + : AppTheme.blackPrimary, + ), + ), + Text( + AppLocalizations.of(context)!.strictTranslate( + filters.values.toList()[index], + ), + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith( + color: model.chosenValue == + filters.keys.toList()[index] + ? AppTheme.white + : AppTheme.blackSecondary, + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ], + ), + ); + }, + ), + ), + ); +} diff --git a/lib/views/after_auth_screens/events/explore_events.dart b/lib/views/after_auth_screens/events/explore_events.dart index ed192eaaf4..cc9d665df7 100644 --- a/lib/views/after_auth_screens/events/explore_events.dart +++ b/lib/views/after_auth_screens/events/explore_events.dart @@ -5,6 +5,7 @@ import 'package:talawa/services/size_config.dart'; import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; import 'package:talawa/view_model/main_screen_view_model.dart'; +import 'package:talawa/views/after_auth_screens/events/event_filter_bottomsheet.dart'; import 'package:talawa/views/after_auth_screens/events/explore_event_dialogue.dart'; import 'package:talawa/views/base_view.dart'; import 'package:talawa/widgets/event_card.dart'; @@ -29,12 +30,16 @@ class ExploreEvents extends StatelessWidget { appBar: AppBar( // AppBar returns widget for the header. backgroundColor: Theme.of(context).primaryColor, - key: const Key("ExploreEventsAppBar"), + key: const Key( + "ExploreEventsAppBar", + ), elevation: 0.0, automaticallyImplyLeading: false, centerTitle: true, title: Text( - AppLocalizations.of(context)!.strictTranslate('Explore Events'), + AppLocalizations.of(context)!.strictTranslate( + 'Explore Events', + ), style: Theme.of(context).textTheme.titleLarge!.copyWith( fontWeight: FontWeight.w600, fontSize: 20, @@ -42,8 +47,12 @@ class ExploreEvents extends StatelessWidget { ), leading: IconButton( // returns a button of menu icon to redirect to home. - color: Theme.of(context).iconTheme.color, - icon: const Icon(Icons.menu), + color: Theme.of( + context, + ).iconTheme.color, + icon: const Icon( + Icons.menu, + ), onPressed: () => MainScreenViewModel.scaffoldKey.currentState!.openDrawer(), ), @@ -64,7 +73,10 @@ class ExploreEvents extends StatelessWidget { ), ); }, - icon: const Icon(Icons.search, size: 20), + icon: Icon( + Icons.search, + size: (SizeConfig.safeBlockHorizontal ?? 4) * 5, + ), ) : const SizedBox(), ), @@ -73,7 +85,9 @@ class ExploreEvents extends StatelessWidget { // if the model is still fetching the events list then renders the Circular Progress Indicator // else render refresh icon along with the list of searched events for exploration. body: model.isBusy - ? const Center(child: CircularProgressIndicator()) + ? const Center( + child: CircularProgressIndicator(), + ) : RefreshIndicator( onRefresh: () async => model.refreshEvents(), child: Stack( @@ -91,25 +105,56 @@ class ExploreEvents extends StatelessWidget { MainAxisAlignment.spaceBetween, children: [ Expanded( - flex: 3, - child: Card( - color: Theme.of(context) - .colorScheme - .onPrimary, - elevation: 2, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, - ), - //width: SizeConfig.screenWidth! * 0.45, - child: DropdownButtonHideUnderline( - child: dropDownList(model, context), + flex: 2, + child: GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + builder: (_) { + return dropDownList( + model, + context, + ); + }, + ); + }, + child: Card( + color: Theme.of(context) + .colorScheme + .onPrimary, + child: Container( + padding: EdgeInsets.symmetric( + vertical: (SizeConfig + .safeBlockHorizontal ?? + 4) * + 3, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + SizedBox( + width: SizeConfig + .safeBlockHorizontal, + ), + Text( + AppLocalizations.of(context)! + .strictTranslate( + "Filters", + ), + ), + SizedBox( + width: SizeConfig + .safeBlockHorizontal, + ), + ], + ), ), ), ), ), Expanded( - flex: 2, + flex: 3, child: GestureDetector( onTap: () { showDialog( @@ -128,27 +173,39 @@ class ExploreEvents extends StatelessWidget { .colorScheme .onPrimary, child: Container( - padding: const EdgeInsets.symmetric( - vertical: 12, + padding: EdgeInsets.symmetric( + vertical: (SizeConfig + .safeBlockHorizontal ?? + 4) * + 3, ), // width: SizeConfig.screenWidth! * 0.30, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + SizedBox( + width: SizeConfig + .safeBlockHorizontal, + ), const Icon( Icons.calendar_today, color: Color(0xff524F4F), ), - const SizedBox( - width: 8, + SizedBox( + width: SizeConfig + .safeBlockHorizontal, ), Text( AppLocalizations.of(context)! .strictTranslate( - "Add Date", + "Filter by Date", ), ), + SizedBox( + width: SizeConfig + .safeBlockHorizontal, + ), ], ), ), @@ -244,41 +301,4 @@ class ExploreEvents extends StatelessWidget { }, ); } - - /// Shows a list of dropdown taken from `model` and `context`. - /// - /// **params**: - /// * `model`: contains the events data - /// * `context`: the overall context of UI - /// - /// **returns**: - /// * `Widget`: the dropdown - Widget dropDownList(ExploreEventsViewModel model, BuildContext context) { - return DropdownButton( - key: homeModel?.keySECategoryMenu, - value: model.chosenValue, - isExpanded: true, - items: [ - 'All Events', - 'Created Events', - 'Registered Events', - 'Public Events', - 'Private Events', - ].map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - AppLocalizations.of(context)!.strictTranslate(value), - style: Theme.of(context) - .textTheme - .titleLarge! - .copyWith(color: Theme.of(context).colorScheme.secondary), - ), - ); - }).toList(), - onChanged: (value) { - model.choseValueFromDropdown(value!); - }, - ); - } } diff --git a/lib/views/after_auth_screens/profile/edit_profile_page.dart b/lib/views/after_auth_screens/profile/edit_profile_page.dart index 05b74a1fc5..32de5599d7 100644 --- a/lib/views/after_auth_screens/profile/edit_profile_page.dart +++ b/lib/views/after_auth_screens/profile/edit_profile_page.dart @@ -22,21 +22,18 @@ class _EditProfilePageState extends State { return Scaffold( key: const Key('EditProfileScreenScaffold'), appBar: AppBar( - // returns a header for the page. backgroundColor: Theme.of(context).primaryColor, elevation: 0.0, title: Text( - // Title of the app bar(header). AppLocalizations.of(context)!.strictTranslate('Profile'), key: const Key('ProfileText'), style: Theme.of(context).textTheme.titleLarge!.copyWith( fontWeight: FontWeight.w600, - fontSize: 20, + fontSize: SizeConfig.screenHeight! * 0.03, ), ), ), body: SingleChildScrollView( - // SingleChildScrollView is a box in which a single widget can be scrolled. child: Column( children: [ SizedBox( @@ -47,69 +44,93 @@ class _EditProfilePageState extends State { children: [ // if the profile pic is not empty then render Circle Avatar with image as background image // else render Circle Avatar with grey background color. - model.imageFile != null - ? CircleAvatar( - radius: SizeConfig.screenHeight! * 0.082, - backgroundImage: Image.file( + CircleAvatar( + key: const Key('profilepic'), + radius: SizeConfig.screenHeight! * 0.082, + backgroundImage: model.imageFile != null + ? Image.file( model.imageFile!, fit: BoxFit.fitWidth, - ).image, - ) - : model.user.image != null - ? CircleAvatar( - key: const Key('UserImageInDb'), - radius: SizeConfig.screenHeight! * 0.082, - backgroundImage: - NetworkImage(model.user.image!), - ) - : CircleAvatar( - key: const Key('UserImageNotInDb'), - radius: SizeConfig.screenHeight! * 0.082, - backgroundColor: Colors.grey.withOpacity(0.2), - child: Text( - model.user.firstName! - .substring(0, 1) - .toUpperCase() + - model.user.lastName! - .substring(0, 1) - .toUpperCase(), + ).image + : model.user.image != null + ? NetworkImage(model.user.image!) + : null, + backgroundColor: + model.imageFile == null && model.user.image == null + ? Colors.grey.withOpacity(0.2) + : null, + child: model.imageFile == null + ? model.user.image == null + ? Text( + '${model.user.firstName![0].toUpperCase()}${model.user.lastName![0].toUpperCase()}', style: Theme.of(context) .textTheme .headlineMedium, - ), - ), + ) + : null + : null, + ), Positioned( bottom: 0, right: 0, child: InkWell( - // button to remove or set the profile image. key: const Key('AddRemoveImageButton'), onTap: () { - // if image is null the function will be get getImageFromGallery() - // else removeImage() + // modal sheet for image selection from camera or gallery. model.imageFile == null - ? model.getImageFromGallery(camera: true) + ? showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Container( + height: + SizeConfig.screenHeight! * 0.135, + padding: const EdgeInsets.all(17), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + _createModalSheetButton( + context, + Icons.camera_alt, + 'Camera', + () { + Navigator.of(context).pop(); + model.selectImage( + camera: true, + ); + }, + ), + _createModalSheetButton( + context, + Icons.photo_library, + 'Gallery', + () { + Navigator.of(context).pop(); + model.selectImage(); + }, + ), + ], + ), + ); + }, + ) : model.removeImage(); }, - child: model.imageFile == null - ? CircleAvatar( - radius: SizeConfig.screenHeight! * 0.034, - backgroundColor: - Theme.of(context).colorScheme.secondary, - child: const Icon( + child: CircleAvatar( + radius: SizeConfig.screenHeight! * 0.034, + backgroundColor: model.imageFile == null + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.secondary, + child: model.imageFile == null + ? const Icon( Icons.photo_camera, color: Colors.white, - ), - ) - : CircleAvatar( - radius: SizeConfig.screenHeight! * 0.02, - backgroundColor: - Theme.of(context).colorScheme.secondary, - child: const Icon( + ) + : const Icon( Icons.close, color: Colors.white, ), - ), + ), ), ), ], @@ -227,9 +248,16 @@ class _EditProfilePageState extends State { ), ), const Divider(), - // button to update the profile. TextButton( - onPressed: () {}, + key: const Key('updatebtn'), + onPressed: () { + model.updateUserProfile( + firstName: model.firstNameTextController.text, + newImage: model.imageFile, + lastName: model.lastNameTextController.text, + ); + FocusScope.of(context).unfocus(); + }, child: Text( AppLocalizations.of(context)!.strictTranslate('Update'), ), @@ -241,4 +269,36 @@ class _EditProfilePageState extends State { }, ); } + + /// Button for the different image selection methods. + /// + /// **params**: + /// * `context`:context for the sheet + /// * `icon`: icon for the method + /// * `label`: label for the method + /// * `onTap`: onTap funtionality for the method + /// + /// **returns**: + /// * `Widget`: Icon Button for selecting different image selection method. + Widget _createModalSheetButton( + BuildContext context, + IconData icon, + String label, + VoidCallback onTap, + ) { + return GestureDetector( + key: Key('select$label'), + onTap: onTap, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: SizeConfig.screenHeight! * 0.05, + ), + Text(AppLocalizations.of(context)!.strictTranslate(label)), + ], + ), + ); + } } diff --git a/lib/views/after_auth_screens/profile/profile_page.dart b/lib/views/after_auth_screens/profile/profile_page.dart index 5f8f2864ed..ab058478d8 100644 --- a/lib/views/after_auth_screens/profile/profile_page.dart +++ b/lib/views/after_auth_screens/profile/profile_page.dart @@ -51,21 +51,22 @@ class ProfilePage extends StatelessWidget { AppLocalizations.of(context)!.strictTranslate('Profile'), style: Theme.of(context).textTheme.titleLarge!.copyWith( // fontWeight: FontWeight.w600, - fontSize: 20, + fontSize: SizeConfig.screenHeight! * 0.03, fontFamily: 'open-sans', color: Colors.white, ), ), actions: [ IconButton( + key: const Key('settingIcon'), onPressed: () { showModalBottomSheet( context: context, builder: (BuildContext context) { return Container( - height: 200, + key: const Key('sheetContainer'), + height: SizeConfig.screenHeight! * 0.17, decoration: const BoxDecoration( - color: Colors.white, borderRadius: BorderRadius.only( bottomLeft: Radius.zero, bottomRight: Radius.zero, @@ -86,11 +87,14 @@ class ProfilePage extends StatelessWidget { child: const Text( "Edit Profile", style: TextStyle( - color: Colors.black38, fontFamily: 'open-sans', ), ), ), + Divider( + endIndent: SizeConfig.screenHeight! * 0.03, + indent: SizeConfig.screenHeight! * 0.03, + ), TextButton( onPressed: () { model.logout(context); @@ -98,7 +102,6 @@ class ProfilePage extends StatelessWidget { child: const Text( "Log Out", style: TextStyle( - color: Colors.black38, fontFamily: 'open-sans', ), ), @@ -118,191 +121,198 @@ class ProfilePage extends StatelessWidget { // else renders the widget. body: model.isBusy ? const CircularProgressIndicator() - : SingleChildScrollView( - child: Column( - children: [ - SizedBox( - height: SizeConfig.screenHeight! * 0.01, - ), - Row( - children: [ - Expanded( - flex: 1, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: CustomAvatar( - isImageNull: model.currentUser.image == null, - firstAlphabet: model.currentUser.firstName! - .substring(0, 1), - imageUrl: model.currentUser.image, - fontSize: Theme.of(context) - .textTheme - .titleLarge! - .fontSize, - maxRadius: 30, - ), - ), - ), - Expanded( - flex: 3, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - '${model.currentUser.firstName!} ${model.currentUser.lastName!}', - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontFamily: 'open-sans', + : RefreshIndicator( + onRefresh: () async => model.initialize(), + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: SizeConfig.screenHeight! * 0.01, + ), + Row( + children: [ + Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CustomAvatar( + key: const Key('profilepic'), + isImageNull: model.currentUser.image == null, + firstAlphabet: model.currentUser.firstName! + .substring(0, 1), + imageUrl: model.currentUser.image, + fontSize: Theme.of(context) + .textTheme + .titleLarge! + .fontSize, + maxRadius: SizeConfig.screenHeight! * 0.02, ), ), ), - ), - Expanded( - flex: 1, - child: IconButton( - icon: Icon( - Icons.share, - color: Theme.of(context).colorScheme.secondary, + Expanded( + flex: 3, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${model.currentUser.firstName!} ${model.currentUser.lastName!}', + style: TextStyle( + color: Colors.white, + fontSize: SizeConfig.screenHeight! * 0.025, + fontFamily: 'open-sans', + ), + ), ), - onPressed: () => model.invite(context), ), - ), - ], - ), - const SizedBox( - height: 20, - ), - TalawaPluginProvider( - pluginName: "Donation", - visible: true, - child: Column( - children: [ - RaisedRoundedButton( - key: homeModel!.keySPDonateUs, - buttonLabel: - AppLocalizations.of(context)!.strictTranslate( - 'Donate to the Community', + Expanded( + flex: 1, + child: IconButton( + key: const Key('inviteicon'), + icon: Icon( + Icons.share, + color: + Theme.of(context).colorScheme.secondary, + ), + onPressed: () => model.invite(context), ), - onTap: () => donate(context, model), - textColor: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, - backgroundColor: Theme.of(context) - .colorScheme - .secondaryContainer, ), ], ), - ), - SizedBox( - height: 600, - width: double.infinity, - child: ContainedTabBarView( - tabs: [ - const Tab(text: 'Posts'), - const Tab(text: 'Events'), - const Tab(text: 'Tasks'), - ], - views: [ - ColoredBox( - color: Theme.of(context).colorScheme.background, - child: GridView.count( - mainAxisSpacing: 5, - crossAxisCount: 3, - children: [ - Image.asset('assets/images/pfp2.png'), - Image.asset('assets/images/pfp2.png'), - Image.asset('assets/images/pfp2.png'), - Image.asset('assets/images/pfp2.png'), - Image.asset('assets/images/pfp2.png'), - ], + SizedBox( + height: SizeConfig.screenHeight! * 0.02, + ), + TalawaPluginProvider( + pluginName: "Donation", + visible: true, + child: Column( + children: [ + RaisedRoundedButton( + key: homeModel!.keySPDonateUs, + buttonLabel: AppLocalizations.of(context)! + .strictTranslate( + 'Donate to the Community', + ), + onTap: () => donate(context, model), + textColor: Theme.of(context) + .inputDecorationTheme + .focusedBorder! + .borderSide + .color, + backgroundColor: Theme.of(context) + .colorScheme + .secondaryContainer, ), - ), - Container( - color: Theme.of(context).colorScheme.background, - ), - ColoredBox( - color: Theme.of(context).colorScheme.onPrimary, - child: GestureDetector( - onTap: () { - navigationService - .pushScreen(Routes.userTasks); - }, + ], + ), + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.6, + width: double.infinity, + child: ContainedTabBarView( + tabs: [ + const Tab(text: 'Posts'), + const Tab(text: 'Events'), + const Tab(text: 'Tasks'), + ], + views: [ + ColoredBox( + color: Theme.of(context).colorScheme.background, + child: GridView.count( + mainAxisSpacing: 5, + crossAxisCount: 3, + children: [ + Image.asset('assets/images/pfp2.png'), + Image.asset('assets/images/pfp2.png'), + Image.asset('assets/images/pfp2.png'), + Image.asset('assets/images/pfp2.png'), + Image.asset('assets/images/pfp2.png'), + ], + ), ), - ), - ], + Container( + color: Theme.of(context).colorScheme.background, + ), + ColoredBox( + color: Theme.of(context).colorScheme.onPrimary, + child: GestureDetector( + key: const Key('tastscrn'), + onTap: () { + navigationService + .pushScreen(Routes.userTasks); + }, + ), + ), + ], + ), ), - ), - SizedBox( - height: SizeConfig.screenHeight! * 0.67, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: SizeConfig.screenHeight! * 0.01, - ), - SizedBox( - height: SizeConfig.screenHeight! * 0.05, - ), + SizedBox( + height: SizeConfig.screenHeight! * 0.67, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + height: SizeConfig.screenHeight! * 0.01, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.05, + ), - /// `Donation` acts as plugin. If visible is true the it will be always visible. - /// even if it's uninstalled by the admin (for development purposes) - //TODO: custom tile for Invitation. - CustomListTile( - key: homeModel!.keySPInvite, - index: 3, - type: TileType.option, - option: Options( - icon: Icon( - Icons.share, - color: - Theme.of(context).colorScheme.secondary, - size: 30, + /// `Donation` acts as plugin. If visible is true the it will be always visible. + /// even if it's uninstalled by the admin (for development purposes) + //TODO: custom tile for Invitation. + CustomListTile( + key: homeModel!.keySPInvite, + index: 3, + type: TileType.option, + option: Options( + icon: Icon( + Icons.share, + color: + Theme.of(context).colorScheme.secondary, + size: SizeConfig.screenHeight! * 0.025, + ), + // title + title: AppLocalizations.of(context)! + .strictTranslate('Invite'), + // subtitle + subtitle: AppLocalizations.of(context)! + .strictTranslate('Invite to org'), ), - // title - title: AppLocalizations.of(context)! - .strictTranslate('Invite'), - // subtitle - subtitle: AppLocalizations.of(context)! - .strictTranslate('Invite to org'), + // on tap call the invite function + onTapOption: () => model.invite(context), ), - // on tap call the invite function - onTapOption: () => model.invite(context), - ), - SizedBox( - height: SizeConfig.screenHeight! * 0.05, - ), - // Custom tile for Logout option. - //TODO: logout - // CustomListTile( - // key: homeModel!.keySPLogout, - // index: 3, - // type: TileType.option, - // option: Options( - // icon: Icon( - // Icons.logout, - // color: - // Theme.of(context).colorScheme.secondary, - // size: 30, - // ), - // title: AppLocalizations.of(context)! - // .strictTranslate('Log out'), - // subtitle: AppLocalizations.of(context)! - // .strictTranslate('Log out from Talawa'), - // ), - // // on tap calls the logout function - // onTapOption: () => model.logout(context), - // ), - SizedBox( - height: SizeConfig.screenHeight! * 0.05, - ), - FromPalisadoes(key: homeModel!.keySPPalisadoes), - ], + SizedBox( + height: SizeConfig.screenHeight! * 0.05, + ), + // Custom tile for Logout option. + //TODO: logout + // CustomListTile( + // key: homeModel!.keySPLogout, + // index: 3, + // type: TileType.option, + // option: Options( + // icon: Icon( + // Icons.logout, + // color: + // Theme.of(context).colorScheme.secondary, + // size: 30, + // ), + // title: AppLocalizations.of(context)! + // .strictTranslate('Log out'), + // subtitle: AppLocalizations.of(context)! + // .strictTranslate('Log out from Talawa'), + // ), + // // on tap calls the logout function + // onTapOption: () => model.logout(context), + // ), + SizedBox( + height: SizeConfig.screenHeight! * 0.05, + ), + FromPalisadoes(key: homeModel!.keySPPalisadoes), + ], + ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/widgets/multi_reaction.dart b/lib/widgets/multi_reaction.dart index 2f054594c2..2abec4d95e 100644 --- a/lib/widgets/multi_reaction.dart +++ b/lib/widgets/multi_reaction.dart @@ -2,8 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; import 'package:flutter_svg/flutter_svg.dart'; +/// Reaction Button with multiple Emojis. class MultiReactButton extends StatefulWidget { const MultiReactButton({super.key, required this.toggle}); + + /// Toggle Function for react Button. final VoidCallback toggle; @override diff --git a/lib/widgets/post_widget.dart b/lib/widgets/post_widget.dart index 10cd2681dd..7ec420a3ad 100644 --- a/lib/widgets/post_widget.dart +++ b/lib/widgets/post_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/services/graphql_config.dart'; import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/widgets_view_models/like_button_view_model.dart'; import 'package:talawa/views/base_view.dart'; @@ -53,7 +54,8 @@ class NewsPost extends StatelessWidget { isImageNull: post.creator!.image == null, firstAlphabet: post.creator!.firstName!.substring(0, 1).toUpperCase(), - imageUrl: post.creator!.image, + imageUrl: + "${'${GraphqlConfig.orgURI}'.replaceFirst('/graphql', '')}/${post.creator!.image}", fontSize: 20, ), title: Row( diff --git a/pubspec.lock b/pubspec.lock index 1e552425c9..1f550b9d99 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -133,26 +133,26 @@ packages: dependency: "direct main" description: name: cached_network_image - sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" characters: dependency: transitive description: @@ -285,10 +285,10 @@ packages: dependency: "direct main" description: name: currency_picker - sha256: "5b87c259dbdb4e032c6b9abd22158782868505b5217b453c6c36445612a3d34c" + sha256: eb75deb7bc92e3f31e1b8ad4efacf71371e8e49d7a0eebd1c1a8e9fae58cc23d url: "https://pub.dev" source: hosted - version: "2.0.19" + version: "2.0.20" custom_lint: dependency: "direct dev" description: @@ -442,10 +442,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: bb5cd63ff7c91d6efe452e41d0d0ae6348925c82eafd10ce170ef585ea04776e + sha256: "892ada16046d641263f30c72e7432397088810a84f34479f6677494802a2b535" url: "https://pub.dev" source: hosted - version: "16.2.0" + version: "16.3.0" flutter_local_notifications_linux: dependency: transitive description: @@ -961,10 +961,10 @@ packages: dependency: "direct dev" description: name: lint - sha256: "77b3777e8e9adca8e942da1e835882ae3248dfa00488a2ebbdbc1f1a4aa3f4a7" + sha256: d758a5211fce7fd3f5e316f804daefecdc34c7e53559716125e6da7388ae8565 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" logging: dependency: transitive description: @@ -1494,26 +1494,26 @@ packages: dependency: "direct main" description: name: syncfusion_flutter_calendar - sha256: d57808a698c6ab1f0b79445822e84fdbcbb99ed7a6e33a4171a5265ad32f4b25 + sha256: b31182b348742b0f2285849179490afd89e321514b8e0eadeab6d7377373c9f2 url: "https://pub.dev" source: hosted - version: "23.2.7" + version: "24.1.43" syncfusion_flutter_core: dependency: transitive description: name: syncfusion_flutter_core - sha256: a2427697bfad5b611db78ea4c4daef82d3350b83c729a8dc37959662a31547f9 + sha256: "1b40729aa10a727150a6cc56e532c770f4baded83846fca8700efd908d0f4d0a" url: "https://pub.dev" source: hosted - version: "23.2.7" + version: "24.1.43" syncfusion_flutter_datepicker: dependency: "direct main" description: name: syncfusion_flutter_datepicker - sha256: b3340a7786f674d18bd22c226358648985e7631734dfc4aae09fdcfb71c09156 + sha256: "3f9a8e8b585dd992d2321c899ec8d914634fb99eba02cf7a91c48a098bf62820" url: "https://pub.dev" source: hosted - version: "23.2.7" + version: "24.1.43" synchronized: dependency: transitive description: @@ -1778,5 +1778,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0 <3.13.0" - flutter: ">=3.13.0" + dart: ">=3.2.0 <3.13.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5a2fdaf738..c0b7595220 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,13 +20,13 @@ dependencies: ################################ auto_size_text: ^3.0.0 - cached_network_image: ^3.1.0 + cached_network_image: ^3.3.1 connectivity_plus: ^5.0.2 contained_tab_bar_view: ^0.8.0 crypto: ^3.0.3 cupertino_icons: ^1.0.3 - currency_picker: ^2.0.19 + currency_picker: ^2.0.20 ############## Remove ########## # custom_lint_builder: ^0.4.0 @@ -37,7 +37,7 @@ dependencies: sdk: flutter flutter_braintree: ^3.0.0 flutter_cache_manager: ^3.3.1 - flutter_local_notifications: ^16.2.0 + flutter_local_notifications: ^16.3.0 flutter_localizations: sdk: flutter flutter_reaction_button: ^3.0.0+3 @@ -69,8 +69,8 @@ dependencies: shared_preferences: ^2.2.2 shimmer: ^3.0.0 social_share: ^2.2.1 - syncfusion_flutter_calendar: ^23.2.7 - syncfusion_flutter_datepicker: any + syncfusion_flutter_calendar: ^24.1.43 + syncfusion_flutter_datepicker: 24.1.43 timelines: ^0.1.0 tutorial_coach_mark: ^1.2.11 uni_links: ^0.5.1 @@ -86,7 +86,7 @@ dev_dependencies: hive_generator: ^2.0.1 json_serializable: ^6.7.1 - lint: ^2.2.0 + lint: ^2.3.0 mocktail_image_network: ^1.0.0 talawa_lint: diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart index 6bafa6645d..71ebf67576 100644 --- a/test/helpers/test_helpers.dart +++ b/test/helpers/test_helpers.dart @@ -25,6 +25,7 @@ import 'package:talawa/services/comment_service.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/services/event_service.dart'; import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/org_service.dart'; import 'package:talawa/services/post_service.dart'; @@ -53,6 +54,7 @@ import 'package:talawa/view_model/theme_view_model.dart'; import 'package:talawa/view_model/widgets_view_models/custom_drawer_view_model.dart'; import 'package:talawa/view_model/widgets_view_models/like_button_view_model.dart'; import 'package:talawa/view_model/widgets_view_models/progress_dialog_view_model.dart'; +import '../service_tests/image_service_test.dart'; import '../views/main_screen_test.dart'; import 'test_helpers.mocks.dart'; @@ -234,15 +236,18 @@ GraphqlConfig getAndRegisterGraphqlConfig() { }); when(service.authClient()).thenAnswer((realInvocation) { - final AuthLink authLink = - AuthLink(getToken: () async => 'Bearer ${GraphqlConfig.token}'); - final Link finalAuthLink = authLink.concat(service.httpLink); - return GraphQLClient( - cache: GraphQLCache(partialDataPolicy: PartialDataCachePolicy.accept), - link: finalAuthLink, - ); + // final AuthLink authLink = + // AuthLink(getToken: () async => 'Bearer ${GraphqlConfig.token}'); + // final Link finalAuthLink = authLink.concat(service.httpLink); + // return GraphQLClient( + // cache: GraphQLCache(partialDataPolicy: PartialDataCachePolicy.accept), + // link: finalAuthLink, + // ); + return locator(); }); + when(service.getToken()).thenAnswer((_) async => "sample_token"); + locator.registerSingleton(service); return service; } @@ -427,6 +432,13 @@ ImageCropper getAndRegisterImageCropper() { return service; } +ImageService getAndRegisterImageService() { + _removeRegistrationIfExists(); + final service = MockImageService(); + locator.registerLazySingleton(() => service); + return service; +} + ImagePicker getAndRegisterImagePicker() { _removeRegistrationIfExists(); final service = MockImagePicker(); diff --git a/test/helpers/test_helpers.mocks.dart b/test/helpers/test_helpers.mocks.dart index a5557bb59c..4f51950a68 100644 --- a/test/helpers/test_helpers.mocks.dart +++ b/test/helpers/test_helpers.mocks.dart @@ -2822,14 +2822,25 @@ class MockCommentService extends _i2.Mock implements _i35.CommentService { ) as _i4.Future); @override - _i4.Future getCommentsForPost(String? postId) => (super.noSuchMethod( - Invocation.method( - #getCommentsForPost, - [postId], - ), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) as _i4.Future); + _i4.Future> getCommentsForPost(String? postId) { + final result = super.noSuchMethod( + Invocation.method( + #getCommentsForPost, + [postId], + ), + returnValue: _i4.Future>.value( + []), // Provide an empty list as a default value + returnValueForMissingStub: _i4.Future>.value([]), + ); + + // Check if the result is null, and return an empty list if it is + if (result == null) { + return _i4.Future>.value([]); + } + + // Otherwise, cast the result to List + return result as _i4.Future>; + } } /// A class which mocks [AppTheme]. diff --git a/test/helpers/test_locator.dart b/test/helpers/test_locator.dart index 1cfc71fddf..f3a58d3bb1 100644 --- a/test/helpers/test_locator.dart +++ b/test/helpers/test_locator.dart @@ -9,6 +9,7 @@ import 'package:talawa/services/comment_service.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/services/event_service.dart'; import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/org_service.dart'; import 'package:talawa/services/post_service.dart'; @@ -16,6 +17,7 @@ import 'package:talawa/services/size_config.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; import 'package:talawa/services/user_config.dart'; import 'package:talawa/utils/queries.dart'; +import 'package:talawa/view_model/access_request_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/chat_view_models/direct_chat_view_model.dart'; import 'package:talawa/view_model/after_auth_view_models/chat_view_models/select_contact_view_model.dart'; @@ -55,8 +57,9 @@ final eventService = locator(); final commentsService = locator(); final postService = locator(); final mainScreenViewModel = locator(); -final imageCropper = locator(); +final imageService = locator(); final imagePicker = locator(); +final imageCropper = locator(); void testSetupLocator() { //services @@ -74,8 +77,9 @@ void testSetupLocator() { locator.registerLazySingleton(() => EventService()); locator.registerLazySingleton(() => CommentService()); locator.registerLazySingleton(() => MultiMediaPickerService()); - locator.registerLazySingleton(() => ImageCropper()); + locator.registerLazySingleton(() => ImageService()); locator.registerLazySingleton(() => ImagePicker()); + locator.registerLazySingleton(() => ImageCropper()); locator.registerSingleton(() => OrganizationService()); //graphql @@ -95,6 +99,7 @@ void testSetupLocator() { locator.registerFactory(() => LoginViewModel()); locator.registerFactory(() => SelectOrganizationViewModel()); + locator.registerFactory(() => AccessScreenViewModel()); locator.registerFactory(() => SignupDetailsViewModel()); locator.registerFactory(() => WaitingViewModel()); locator.registerFactory(() => ExploreEventsViewModel()); diff --git a/test/service_tests/comment_service_test.dart b/test/service_tests/comment_service_test.dart index 0fa40c43fe..0792b6b934 100644 --- a/test/service_tests/comment_service_test.dart +++ b/test/service_tests/comment_service_test.dart @@ -1,6 +1,3 @@ -// ignore_for_file: talawa_api_doc -// ignore_for_file: talawa_good_doc_comments - import 'package:flutter_test/flutter_test.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:mockito/mockito.dart'; @@ -8,7 +5,7 @@ import 'package:talawa/locator.dart'; import 'package:talawa/services/comment_service.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/utils/comment_queries.dart'; - +import 'package:talawa/utils/post_queries.dart'; import '../helpers/test_helpers.dart'; void main() { @@ -48,63 +45,63 @@ void main() { }); test('test for getCommentsForPost', () async { final dataBaseMutationFunctions = locator(); - final String getCommmentQuery = - CommentQueries().getPostsComments('Ayush s postid'); + PostQueries().getPostById('Ayush s postid'); + when( dataBaseMutationFunctions.gqlAuthMutation(getCommmentQuery), ).thenAnswer( (_) async => QueryResult( options: QueryOptions(document: gql(getCommmentQuery)), data: { - 'commentsByPost': [ - { - 'creator': { - '_id': '123', - 'firstName': 'John', - 'lastName': 'Doe', - 'email': 'test@test.com', - }, - 'createdAt': '123456', - 'text': 'test text', - 'post': 'test post', - 'likeCount': 'test count', - }, - { - 'creator': { - '_id': '123', - 'firstName': 'Ayush', - 'lastName': 'Doe', - 'email': 'test@test.com', + 'post': { + 'comments': [ + { + 'creator': { + '_id': '123', + 'firstName': 'John', + 'lastName': 'Doe', + 'email': 'test@test.com', + }, + 'createdAt': '123456', + 'text': 'test text', + 'post': 'test post', + 'likeCount': 'test count', }, - 'createdAt': '123456', - 'text': 'test text', - 'post': 'test post', - 'likeCount': 'test count', - }, - { - 'creator': { - '_id': '123', - 'firstName': 'john', - 'lastName': 'chauhdary', - 'email': 'test@test.com', + { + 'creator': { + '_id': '123', + 'firstName': 'Ayush', + 'lastName': 'Doe', + 'email': 'test@test.com', + }, + 'createdAt': '123456', + 'text': 'test text', + 'post': 'test post', + 'likeCount': 'test count', }, - 'createdAt': '123456', - 'text': 'test text', - 'post': 'test post', - 'likeCount': 'test count', - } - ], + { + 'creator': { + '_id': '123', + 'firstName': 'john', + 'lastName': 'chauhdary', + 'email': 'test@test.com', + }, + 'createdAt': '123456', + 'text': 'test text', + 'post': 'test post', + 'likeCount': 'test count', + } + ], + }, }, source: QueryResultSource.network, ), ); final service = CommentService(); - final result = await service.getCommentsForPost('Ayush s postid'); - print(result); if (result.toString().contains('[{creator: ' '{' '_id: 123, ' @@ -141,22 +138,178 @@ void main() { final dataBaseMutationFunctions = locator(); final String getCommmentQuery = - CommentQueries().getPostsComments('Ayush'); + PostQueries().getPostById('Ayush s postid'); when( dataBaseMutationFunctions.gqlAuthMutation(getCommmentQuery), ).thenAnswer( (_) async => QueryResult( options: QueryOptions(document: gql(getCommmentQuery)), - data: null, + data: { + 'post': { + 'comments': [], + }, + }, + source: QueryResultSource.network, + ), + ); + + final service = CommentService(); + final result = await service.getCommentsForPost('Ayush postid'); + + if (result.toString().contains('[{creator: ' + '{' + '_id: 123, ' + 'firstName: John, ' + 'lastName: Doe, ' + 'email: test@test.com},' + ' createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}, ' + '{creator: ' + '{_id: 123, ' + 'firstName: Ayush, ' + 'lastName: Doe, ' + 'email: test@test.com}, ' + 'createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}, ' + '{creator: {_id: 123,' + ' firstName: john, ' + 'lastName: chauhdary, ' + 'email: test@test.com}, ' + 'createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}]')) { + fail('the result is not maatching'); + } + expect(result, isEmpty); + }); + + test('test for zero comments on post', () async { + final dataBaseMutationFunctions = locator(); + + final String getCommmentQuery = + PostQueries().getPostById('Ayush s postid'); + when( + dataBaseMutationFunctions.gqlAuthMutation(getCommmentQuery), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(getCommmentQuery)), + data: { + 'post': {'comments': []}, + }, source: QueryResultSource.network, ), ); final service = CommentService(); + final result = await service.getCommentsForPost('Ayush postid'); - final result = await service.getCommentsForPost('Ayush'); + if (result.toString().contains('[{creator: ' + '{' + '_id: 123, ' + 'firstName: John, ' + 'lastName: Doe, ' + 'email: test@test.com},' + ' createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}, ' + '{creator: ' + '{_id: 123, ' + 'firstName: Ayush, ' + 'lastName: Doe, ' + 'email: test@test.com}, ' + 'createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}, ' + '{creator: {_id: 123,' + ' firstName: john, ' + 'lastName: chauhdary, ' + 'email: test@test.com}, ' + 'createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}]')) { + fail('the result is not maatching'); + } + expect(result, isEmpty); + }); + + test('test when post is null', () async { + final dataBaseMutationFunctions = locator(); + + final String getCommmentQuery = + PostQueries().getPostById('Ayush s postid'); + when( + dataBaseMutationFunctions.gqlAuthMutation(getCommmentQuery), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(getCommmentQuery)), + data: { + 'post': null, + }, + source: QueryResultSource.network, + ), + ); + + final service = CommentService(); + final result = await service.getCommentsForPost('Ayush postid'); + + if (result.toString().contains('[{creator: ' + '{' + '_id: 123, ' + 'firstName: John, ' + 'lastName: Doe, ' + 'email: test@test.com},' + ' createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}, ' + '{creator: ' + '{_id: 123, ' + 'firstName: Ayush, ' + 'lastName: Doe, ' + 'email: test@test.com}, ' + 'createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}, ' + '{creator: {_id: 123,' + ' firstName: john, ' + 'lastName: chauhdary, ' + 'email: test@test.com}, ' + 'createdAt: 123456, ' + 'text: test text, ' + 'post: test post, ' + 'likeCount: test count}]')) { + fail('the result is not maatching'); + } + expect(result, isEmpty); + }); + + test('test when result is null', () async { + final dataBaseMutationFunctions = locator(); + + final String getCommmentQuery = + PostQueries().getPostById('Ayush s postid'); + when( + dataBaseMutationFunctions.gqlAuthMutation(getCommmentQuery), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(getCommmentQuery)), + data: null, + source: QueryResultSource.network, + ), + ); + + final service = CommentService(); + final result = await service.getCommentsForPost('Ayush postid'); - print(result); if (result.toString().contains('[{creator: ' '{' '_id: 123, ' diff --git a/test/service_tests/database_mutations_function_test.dart b/test/service_tests/database_mutations_function_test.dart new file mode 100644 index 0000000000..75b821c2c6 --- /dev/null +++ b/test/service_tests/database_mutations_function_test.dart @@ -0,0 +1,1079 @@ +import 'dart:io'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:hive/hive.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/services/database_mutation_functions.dart'; +import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/utils/queries.dart'; +import '../helpers/test_helpers.dart'; +import '../helpers/test_locator.dart'; + +/// Tests database_mutations_functions.dart. +/// +/// more_info_if_required +/// +/// **params**: +/// None +/// +/// **returns**: +/// None +void main() async { + testSetupLocator(); + locator().test(); + late DataBaseMutationFunctions functionsClass; + final Directory dir = await Directory.systemTemp.createTemp('talawa_test'); + Hive.init(dir.path); + await Hive.openBox('url'); + + const userNotAuthenticated = + GraphQLError(message: 'User is not authenticated'); + + const userNotAuthenticatedrand = + GraphQLError(message: 'User is not authenticatedrand'); + const userNotFound = GraphQLError(message: 'User not found'); + const refreshAccessTokenExpiredException = GraphQLError( + message: + 'Access Token has expired. Please refresh session.: Undefined location', + ); + + const wrongCredentials = GraphQLError(message: 'Invalid credentials'); + + const organizationNotFound = GraphQLError(message: 'Organization not found'); + + const memberRequestExist = + GraphQLError(message: 'Membership Request already exists'); + + const notifFeatureNotInstalled = GraphQLError( + message: + 'Failed to determine project ID: Error while making request: getaddrinfo ENOTFOUND metadata.google.internal. Error code: ENOTFOUND', + ); + + const emailAccountPresent = + GraphQLError(message: 'Email address already exists'); + + final testOrg = OrgInfo.fromJson({ + 'image': 'sampleimg', + 'id': 'XYZ', + 'name': 'Sample1', + 'isPublic': true, + 'creator': {'firstName': 'Shivam', 'lastName': 'Gupta'}, + }); + + setUpAll(() { + registerServices(); + functionsClass = DataBaseMutationFunctions(); + functionsClass.init(); + }); + + group('Database Mutation Functions Tests', () { + testWidgets('Widget Testing 1', (tester) async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: OperationException( + graphqlErrors: [userNotAuthenticated], + linkException: UnknownException( + userNotAuthenticated, + StackTrace.current, + ), + ), + source: QueryResultSource.network, + ), + ); + + await functionsClass.gqlNonAuthQuery(query); + + tester.binding.addPostFrameCallback((_) { + navigationService.showTalawaErrorSnackBar( + "Server not running/wrong url", + MessageType.error, + ); + }); + + await tester.pump(); + }); + + testWidgets('Widget Testing 2', (tester) async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: OperationException( + graphqlErrors: [userNotAuthenticatedrand], + ), + source: QueryResultSource.network, + ), + ); + + await functionsClass.gqlNonAuthQuery(query); + + tester.binding.addPostFrameCallback((_) { + navigationService.showTalawaErrorSnackBar( + "Something went wrong!", + MessageType.error, + ); + }); + + await tester.pump(); + }); + + test('fetchOrgById test in case of successful results', () async { + when(locator().clientToQuery()).thenAnswer( + (_) => locator(), + ); + + when( + locator().mutate( + MutationOptions( + document: gql(queries.fetchOrgById('XYZ')), + ), + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(queries.fetchOrgById('XYZ'))), + data: { + 'organizations': [ + { + 'id': 'XYZ', + 'image': 'sampleimg', + 'name': 'Sample1', + 'isPublic': true, + 'creator': {'firstName': 'Shivam', 'lastName': 'Gupta'}, + }, + ], + }, + source: QueryResultSource.network, + ), + ); + + final org = await functionsClass.fetchOrgById('XYZ') as OrgInfo; + + expect(org.id, testOrg.id); + expect(org.name, testOrg.name); + expect(org.image, testOrg.image); + expect(org.isPublic, testOrg.isPublic); + expect(org.creatorInfo!.firstName, testOrg.creatorInfo!.firstName); + }); + + test('fetchOrgById test in case of exception', () async { + final String query = Queries().fetchOrgById('XYZ'); + final String query2 = Queries().refreshToken('abc'); + final String query3 = Queries().refreshToken('xyz'); + + userConfig.currentUser.refreshToken = 'abc'; + + /// Returns the exception to be thrown in case of refresh token expired + /// + /// more_info_if_required + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Map`: Map of exception to be thrown + Map exp2() { + if (userConfig.currentUser.refreshToken == 'abc') { + userConfig.currentUser.refreshToken = 'xyz'; + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotAuthenticated], + ), + }); + } else { + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotFound], + ), + }); + } + } + + when( + locator().mutate( + MutationOptions(document: gql(query)), + ), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query)), + exception: exp2()['val'], + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query2))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query2)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query3))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query3)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.fetchOrgById('XYZ'); + expect(res, false); + }); + + test('Testing wrong credential error', () async { + when(locator().clientToQuery()).thenAnswer( + (_) => locator(), + ); + + when( + locator().mutate( + MutationOptions( + document: gql( + queries.fetchOrgById('XYZ'), + ), + ), + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(queries.fetchOrgById('XYZ'))), + exception: OperationException(graphqlErrors: [wrongCredentials]), + source: QueryResultSource.network, + ), + ); + + final org = await functionsClass.fetchOrgById('XYZ'); + expect(org, false); + }); + + test('Testing organization not found error', () async { + when(locator().clientToQuery()).thenAnswer( + (_) => locator(), + ); + + when( + locator().mutate( + MutationOptions( + document: gql( + queries.fetchOrgById('XYZ'), + ), + ), + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(queries.fetchOrgById('XYZ'))), + exception: OperationException(graphqlErrors: [organizationNotFound]), + source: QueryResultSource.network, + ), + ); + + final org = await functionsClass.fetchOrgById('XYZ'); + expect(org, false); + }); + + test('Testing memberRequestExist error', () async { + when(locator().clientToQuery()).thenAnswer( + (_) => locator(), + ); + + when( + locator().mutate( + MutationOptions( + document: gql(queries.fetchOrgById('XYZ')), + ), + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(queries.fetchOrgById('XYZ'))), + exception: OperationException(graphqlErrors: [memberRequestExist]), + source: QueryResultSource.network, + ), + ); + + final org = await functionsClass.fetchOrgById('XYZ'); + expect(org, false); + }); + + test('Testing emailAccountPresent error', () async { + when(locator().clientToQuery()).thenAnswer( + (_) => locator(), + ); + + when( + locator().mutate( + MutationOptions(document: gql(queries.fetchOrgById('XYZ'))), + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(queries.fetchOrgById('XYZ'))), + exception: OperationException( + graphqlErrors: [emailAccountPresent], + ), + source: QueryResultSource.network, + ), + ); + + final org = await functionsClass.fetchOrgById('XYZ'); + expect(org, false); + }); + + test('Testing notifFeatureNotInstalled error', () async { + when(locator().clientToQuery()).thenAnswer( + (_) => locator(), + ); + + when( + locator().mutate( + MutationOptions(document: gql(queries.fetchOrgById('XYZ'))), + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(queries.fetchOrgById('XYZ'))), + exception: OperationException( + graphqlErrors: [notifFeatureNotInstalled], + ), + source: QueryResultSource.network, + ), + ); + + final org = await functionsClass.fetchOrgById('XYZ'); + expect(org, false); + }); + + test('Testing gqlAuthQuery function without exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'organizations': [ + { + 'id': 'XYZ', + 'image': 'sampleimg', + 'name': 'Sample1', + 'isPublic': true, + 'creator': {'firstName': 'Shivam', 'lastName': 'Gupta'}, + }, + ], + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlAuthQuery(query) as QueryResult; + final org = OrgInfo.fromJson( + (res.data!['organizations'] as List>)[0], + ); + + expect(org.id, testOrg.id); + expect(org.name, testOrg.name); + expect(org.image, testOrg.image); + expect(org.isPublic, testOrg.isPublic); + expect(org.creatorInfo!.firstName, testOrg.creatorInfo!.firstName); + }); + + test('Testing gqlAuthQuery with false exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: OperationException(graphqlErrors: [userNotFound]), + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlAuthQuery(query); + expect(res, null); + }); + + test('Testing gqlAuthQuery with true exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + final String query2 = Queries().refreshToken('abc'); + final String query3 = Queries().refreshToken('xyz'); + + userConfig.currentUser.refreshToken = 'abc'; + + /// Returns the exception to be thrown in case of refresh token expired + /// + /// more_info_if_required + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Map`: Map of exception to be thrown + Map exp2() { + if (userConfig.currentUser.refreshToken == 'abc') { + userConfig.currentUser.refreshToken = 'xyz'; + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotAuthenticated], + ), + }); + } else { + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotFound], + ), + }); + } + } + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: exp2()['val'], + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query2))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query2)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query3))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query3)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlAuthQuery(query); + expect(res, null); + }); + + test('Test for gql auth mutation', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'organizations': [ + { + 'id': 'XYZ', + 'image': 'sampleimg', + 'name': 'Sample1', + 'isPublic': true, + 'creator': {'firstName': 'Shivam', 'lastName': 'Gupta'}, + }, + ], + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlAuthMutation(query) as QueryResult; + final org = OrgInfo.fromJson( + (res.data!['organizations'] as List>)[0], + ); + + expect(org.id, testOrg.id); + expect(org.name, testOrg.name); + expect(org.image, testOrg.image); + expect(org.isPublic, testOrg.isPublic); + expect(org.creatorInfo!.firstName, testOrg.creatorInfo!.firstName); + }); + + test('Test for gql auth mutation with false exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: OperationException(graphqlErrors: [userNotFound]), + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlAuthMutation(query); + expect(res, null); + }); + + test('Test for gql auth mutation with true exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + final String query2 = Queries().refreshToken('abc'); + final String query3 = Queries().refreshToken('xyz'); + + userConfig.currentUser.refreshToken = 'abc'; + + /// Returns the exception to be thrown in case of refresh token expired + /// + /// more_info_if_required + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Map`: Map of exception to be thrown + Map exp2() { + if (userConfig.currentUser.refreshToken == 'abc') { + userConfig.currentUser.refreshToken = 'xyz'; + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotAuthenticated], + ), + }); + } else { + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotFound], + ), + }); + } + } + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query)), + exception: exp2()['val'], + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query2))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query2)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query3))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query3)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlAuthMutation(query); + expect(res, null); + }); + + test('Test for gql non auth query', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'organizations': [ + { + 'id': 'XYZ', + 'image': 'sampleimg', + 'name': 'Sample1', + 'isPublic': true, + 'creator': {'firstName': 'Shivam', 'lastName': 'Gupta'}, + }, + ], + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlNonAuthQuery(query); + final org = OrgInfo.fromJson( + (res!.data!['organizations'] as List>)[0], + ); + + expect(org.id, testOrg.id); + expect(org.name, testOrg.name); + expect(org.image, testOrg.image); + expect(org.isPublic, testOrg.isPublic); + expect(org.creatorInfo!.firstName, testOrg.creatorInfo!.firstName); + }); + + test('Test for gql non auth mutation', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'organizations': [ + { + 'id': 'XYZ', + 'image': 'sampleimg', + 'name': 'Sample1', + 'isPublic': true, + 'creator': {'firstName': 'Shivam', 'lastName': 'Gupta'}, + }, + ], + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlNonAuthMutation(query) as QueryResult; + final org = OrgInfo.fromJson( + (res.data!['organizations'] as List>)[0], + ); + + expect(org.id, testOrg.id); + expect(org.name, testOrg.name); + expect(org.image, testOrg.image); + expect(org.isPublic, testOrg.isPublic); + expect(org.creatorInfo!.firstName, testOrg.creatorInfo!.firstName); + }); + + test('Test for gql non auth mutation with false exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: OperationException(graphqlErrors: [userNotFound]), + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlNonAuthMutation(query); + expect(res, null); + }); + + test('Test for gql non auth mutation with true exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + final String query2 = Queries().refreshToken('abc'); + final String query3 = Queries().refreshToken('xyz'); + + userConfig.currentUser.refreshToken = 'abc'; + + /// Returns the exception to be thrown in case of refresh token expired + /// + /// more_info_if_required + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Map`: Map of exception to be thrown + Map exp2() { + if (userConfig.currentUser.refreshToken == 'abc') { + userConfig.currentUser.refreshToken = 'xyz'; + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotAuthenticated], + ), + }); + } else { + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotFound], + ), + }); + } + } + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query)), + exception: exp2()['val'], + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query2))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query2)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query3))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query3)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlNonAuthMutation(query); + expect(res, null); + }); + + test('Test for refresh access token', () async { + final String query = Queries().refreshToken('reftok123'); + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + data: { + 'refreshToken': { + 'accessToken': 'acctok123', + 'refreshToken': 'reftok123', + }, + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.refreshAccessToken('reftok123'); + verify( + userConfig.updateAccessToken( + accessToken: 'acctok123', + refreshToken: 'reftok123', + ), + ); + verify(databaseFunctions.init()); + expect(res, true); + }); + + test('Test for refresh access token with false exception', () async { + final String query = Queries().refreshToken('reftok123'); + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: OperationException(graphqlErrors: [userNotFound]), + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.refreshAccessToken('reftok123'); + verify(navigationService.pop()); + expect(res, false); + }); + + test('Test for refresh access token with true exception', () async { + final String query = Queries().refreshToken('x'); + final String query2 = Queries().refreshToken('abc'); + final String query3 = Queries().refreshToken('xyz'); + + userConfig.currentUser.refreshToken = 'abc'; + + /// Returns the exception to be thrown in case of refresh token expired + /// + /// more_info_if_required + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Map`: Map of exception to be thrown + Map exp2() { + if (userConfig.currentUser.refreshToken == 'abc') { + userConfig.currentUser.refreshToken = 'xyz'; + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotAuthenticated], + ), + }); + } else { + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotFound], + ), + }); + } + } + + when( + locator().mutate(MutationOptions(document: gql(query))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query)), + exception: exp2()['val'], + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query2))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query2)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query3))), + ).thenAnswer( + (_) async => QueryResult( + options: MutationOptions(document: gql(query3)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.refreshAccessToken('x'); + expect(res, false); + }); + + test('Test for gql non auth query with link exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: OperationException( + graphqlErrors: [userNotFound], + linkException: UnknownException( + userNotFound, + StackTrace.current, + ), + ), + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlNonAuthQuery(query); + expect(res, null); + }); + + test('Test for gql non auth query with true exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + final String query2 = Queries().refreshToken('abc'); + final String query3 = Queries().refreshToken('xyz'); + + userConfig.currentUser.refreshToken = 'abc'; + + /// Returns the exception to be thrown in case of refresh token expired + /// + /// more_info_if_required + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Map`: Map of exception to be thrown + Map exp2() { + if (userConfig.currentUser.refreshToken == 'abc') { + userConfig.currentUser.refreshToken = 'xyz'; + return Map.from({ + 'val': OperationException( + graphqlErrors: [refreshAccessTokenExpiredException], + ), + }); + } else { + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotFound], + ), + }); + } + } + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: exp2()['val'], + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query2))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query2)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query3))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query3)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlNonAuthQuery(query); + expect(res, null); + }); + test('Test for gql non auth query with false exception', () async { + final String query = Queries().fetchOrgDetailsById('XYZ'); + final String query2 = Queries().refreshToken('abc'); + final String query3 = Queries().refreshToken('xyz'); + + userConfig.currentUser.refreshToken = 'abc'; + + /// Returns the exception to be thrown in case of refresh token expired + /// + /// more_info_if_required + /// + /// **params**: + /// None + /// + /// **returns**: + /// * `Map`: Map of exception to be thrown + Map exp2() { + if (userConfig.currentUser.refreshToken == 'abc') { + userConfig.currentUser.refreshToken = 'xyz'; + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotAuthenticated], + ), + }); + } else { + return Map.from({ + 'val': OperationException( + graphqlErrors: [userNotFound], + ), + }); + } + } + + when(locator().query(QueryOptions(document: gql(query)))) + .thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query)), + exception: exp2()['val'], + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query2))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query2)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + when( + locator().mutate(MutationOptions(document: gql(query3))), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions(document: gql(query3)), + data: { + 'refreshToken': { + 'accessToken': 'testtoken', + 'refreshToken': 'testtoken', + }, + }, + source: QueryResultSource.network, + ), + ); + + final res = await functionsClass.gqlNonAuthQuery(query); + expect(res, null); + }); + }); +} diff --git a/test/service_tests/event_service_test.dart b/test/service_tests/event_service_test.dart index 5c31e587e3..12970710d0 100644 --- a/test/service_tests/event_service_test.dart +++ b/test/service_tests/event_service_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:mockito/mockito.dart'; +import 'package:talawa/models/events/event_model.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/services/event_service.dart'; import 'package:talawa/utils/event_queries.dart'; @@ -103,7 +104,7 @@ void main() { final dataBaseMutationFunctions = locator(); final query = TaskQueries.eventTasks('eventId'); when( - dataBaseMutationFunctions.gqlAuthMutation( + dataBaseMutationFunctions.gqlAuthQuery( EventQueries().registrantsByEvent('eventId'), ), ).thenAnswer( @@ -115,8 +116,8 @@ void main() { source: QueryResultSource.network, ), ); - final services = EventQueries(); - services.registrantsByEvent('eventId'); + final services = EventService(); + services.fetchRegistrantsByEvent('eventId'); }); test('Test getEvents method', () async { @@ -139,8 +140,18 @@ void main() { source: QueryResultSource.network, ), ); - final services = EventQueries(); - services.fetchOrgEvents('OrgId'); + final services = EventService(); + services.getEvents(); + }); + + test('Test dispose method', () { + final eventService = EventService(); + eventService.dispose(); + }); + + test('Test for getters', () { + final model = EventService(); + expect(model.eventStream, isA>()); }); }); } diff --git a/test/service_tests/image_service_test.dart b/test/service_tests/image_service_test.dart new file mode 100644 index 0000000000..cce18ff630 --- /dev/null +++ b/test/service_tests/image_service_test.dart @@ -0,0 +1,101 @@ +// ignore_for_file: talawa_api_doc +// ignore_for_file: talawa_good_doc_comments + +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/services/image_service.dart'; + +import '../helpers/test_helpers.dart'; +import '../helpers/test_locator.dart'; + +class MockImageService extends Mock implements ImageService { + @override + Future convertToBase64(File file) async { + return ""; + } +} + +void main() { + testSetupLocator(); + + setUpAll(() { + registerServices(); + }); + + group('Tests for Crop Image', () { + test("test no image provided for the image cropper", () async { + const path = 'test'; + final file = await imageService.cropImage(imageFile: File(path)); + expect(file?.path, null); + }); + + test("crop image method", () async { + final mockImageCropper = imageCropper; + + const path = "test"; + final fakefile = File(path); + final croppedFile = CroppedFile("fakeCropped"); + + when( + mockImageCropper.cropImage( + sourcePath: "test", + aspectRatioPresets: [ + CropAspectRatioPreset.square, + CropAspectRatioPreset.original, + ], + uiSettings: anyNamed('uiSettings'), + ), + ).thenAnswer((realInvocation) async => croppedFile); + + final file = await imageService.cropImage(imageFile: fakefile); + + expect(file?.path, croppedFile.path); + }); + + test("error in crop image", () async { + final mockImageCropper = locator(); + const path = "test"; + final fakefile = File(path); + when( + mockImageCropper.cropImage( + sourcePath: "test", + aspectRatioPresets: [ + CropAspectRatioPreset.square, + CropAspectRatioPreset.original, + ], + uiSettings: anyNamed('uiSettings'), + ), + ).thenThrow(Exception()); + expect( + imageService.cropImage(imageFile: fakefile), + throwsException, + ); + }); + }); + + group('Tests for convertToBase64', () { + test('convertToBase64 converts file to base64 string', () async { + //using this asset as the test asset + final file = File('assets/images/Group 8948.png'); + final List encodedBytes = file.readAsBytesSync(); + + final fileString = await imageService.convertToBase64(file); + + final List decodedBytes = base64Decode(fileString!); + + expect(decodedBytes, equals(encodedBytes)); + }); + + test( + 'Check if convertToBase64 is working even if wrong file path is provided', + () async { + final file = File('fakePath'); + final fileString = await imageService.convertToBase64(file); + expect(null, fileString); + }); + }); +} diff --git a/test/service_tests/multi_media_pick_service_test.dart b/test/service_tests/multi_media_pick_service_test.dart index d1f887d9f0..1205bc5099 100644 --- a/test/service_tests/multi_media_pick_service_test.dart +++ b/test/service_tests/multi_media_pick_service_test.dart @@ -2,50 +2,30 @@ // ignore_for_file: talawa_good_doc_comments import 'dart:async'; -import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mockito/mockito.dart'; -import 'package:talawa/locator.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/size_config.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; +import 'package:talawa/utils/app_localization.dart'; import '../helpers/test_helpers.dart'; +import '../helpers/test_locator.dart'; void main() { + testSetupLocator(); setUp(() { registerServices(); }); + tearDown(() { + unregisterServices(); + }); + SizeConfig().test(); group('MultiMediaPickerService test', () { - test("test get fileStream", () async { - final model = MultiMediaPickerService(); - expect( - model.fileStream.toString(), - "Instance of '_AsBroadcastStream'", - ); - }); - test("crop image method", () async { - final mockImageCropper = imageCropper; - final model = MultiMediaPickerService(); - - const path = "test"; - final fakefile = File(path); - final croppedFile = CroppedFile("fakeCropped"); - - when( - mockImageCropper.cropImage( - sourcePath: "test", - aspectRatioPresets: [ - CropAspectRatioPreset.square, - CropAspectRatioPreset.original, - ], - uiSettings: anyNamed('uiSettings'), - ), - ).thenAnswer((realInvocation) async => croppedFile); - final file = await model.cropImage(imageFile: fakefile); - // verify(mockImageCropper.cropImage(sourcePath: fakefile.path)); - expect(file?.path, croppedFile.path); - }); test("test get photo from gallery method if camera option is false", () async { final mockImageCropper = locator(); @@ -99,12 +79,6 @@ void main() { final file = await model.getPhotoFromGallery(camera: false); expect(file?.path, null); }); - test("test no image provided for the image cropper", () async { - final model = MultiMediaPickerService(); - const path = 'test'; - final file = await model.cropImage(imageFile: File(path)); - expect(file?.path, null); - }); test("camera access denied", () async { final mockPicker = locator(); final model = MultiMediaPickerService(); @@ -127,35 +101,50 @@ void main() { "MultiMediaPickerService : Exception occurred while choosing photo from the gallery $error", ); }); - test("error in crop image", () async { - final mockImageCropper = locator(); - final model = MultiMediaPickerService(); - const path = "test"; - final fakefile = File(path); - final printed = []; - when( - mockImageCropper.cropImage( - sourcePath: "test", - aspectRatioPresets: [ - CropAspectRatioPreset.square, - CropAspectRatioPreset.original, - ], - uiSettings: anyNamed('uiSettings'), - ), - ).thenThrow(Exception()); - runZoned( - () async { - await model.cropImage(imageFile: fakefile); - }, - zoneSpecification: ZoneSpecification( - print: (self, parent, zone, line) { - printed.add(line); - }, - ), + + testWidgets('Test for permission_denied_dialog success action.', + (tester) async { + final service = MultiMediaPickerService(); + + final Widget app = MaterialApp( + navigatorKey: locator().navigatorKey, + navigatorObservers: [], + locale: const Locale('en'), + supportedLocales: [ + const Locale('en', 'US'), + const Locale('es', 'ES'), + const Locale('fr', 'FR'), + const Locale('hi', 'IN'), + const Locale('zh', 'CN'), + const Locale('de', 'DE'), + const Locale('ja', 'JP'), + const Locale('pt', 'PT'), + ], + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold(body: service.permissionDeniedDialog()), ); + + await tester.pumpWidget(app); + await tester.pumpAndSettle(); + + final settingsFinder = find.textContaining('SETTINGS'); + + expect(settingsFinder, findsOneWidget); + + await tester.tap(settingsFinder); + + verify(navigationService.pop()); + }); + + test("test get fileStream", () async { + final model = MultiMediaPickerService(); expect( - printed[0], - "MultiMediaPickerService : Exception occurred while cropping Image", + model.fileStream.toString(), + "Instance of '_AsBroadcastStream'", ); }); }); diff --git a/test/service_tests/navigation_service_test.dart b/test/service_tests/navigation_service_test.dart index bb361f3929..e40d8f17e8 100644 --- a/test/service_tests/navigation_service_test.dart +++ b/test/service_tests/navigation_service_test.dart @@ -8,7 +8,13 @@ import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/widgets/talawa_error_dialog.dart'; import 'package:talawa/widgets/talawa_error_snackbar.dart'; -// This methods tries to cover all the cases which can be there in future! +/// This methods tries to cover all the cases which can be there in future. +/// +/// **params**: +/// * `settings`: RouteSettings +/// +/// **returns**: +/// * `Route`: Returns Route Type Route _onGenerateTestRoute(RouteSettings settings) { if (settings.name == '/second-screen') { if (settings.arguments == null) { @@ -169,7 +175,6 @@ class _SecondTestScreenState extends State { class ThirdTestScreen extends StatefulWidget { const ThirdTestScreen({super.key, this.arguments}); final String? arguments; - @override State createState() => _ThirdTestScreenState(); } diff --git a/test/service_tests/user_config_test.dart b/test/service_tests/user_config_test.dart index 8f842156de..ef0bd9d787 100644 --- a/test/service_tests/user_config_test.dart +++ b/test/service_tests/user_config_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: talawa_api_doc // ignore_for_file: talawa_good_doc_comments +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -78,7 +79,81 @@ void main() async { setUpAll(() { registerServices(); }); - test('Test for User log out.', () async { + + test('Test for getters & setters.', () { + final model = UserConfig(); + + // model.currentOrgInfoController + expect(model.currentOrgInfoController, isA>()); + + // model.currentOrgName + expect(model.currentOrgName, isA()); + + // model.currenOrg (setter) + model.currentOrg = OrgInfo(name: 'org'); + + // print(model.currentOrgInfoController); + }); + + test('Test for userLoggedIn method.', () async { + final model = UserConfig(); + model.currentUser.id = 'fake_id'; + + userBox.put('user', User(id: 'fake', firstName: 'first')); + + final Map data = { + 'users': [ + { + '_id': '1234567890', + 'firstName': 'John', + 'lastName': 'Doe', + 'email': 'johndoe@example.com', + 'image': 'https://example.com/profile.jpg', + 'accessToken': 'exampleAccessToken', + 'refreshToken': 'exampleRefreshToken', + } + ], + }; + + when( + databaseFunctions.gqlAuthQuery( + queries.fetchUserInfo, + variables: anyNamed('variables'), + ), + ).thenAnswer((_) async { + return QueryResult( + source: QueryResultSource.network, + data: data, + options: QueryOptions(document: gql(queries.fetchUserInfo)), + ); + }); + + // if there is _currentUser. + bool loggedIn = await model.userLoggedIn(); + expect(loggedIn, true); + + userBox.delete('user'); + + // if there is no _currentUser. + loggedIn = await model.userLoggedIn(); + expect(loggedIn, false); + + when( + databaseFunctions.gqlAuthQuery( + queries.fetchUserInfo, + variables: anyNamed('variables'), + ), + ).thenAnswer((_) async { + throw Exception('Simulated Exception.'); + }); + + // show couldn't update errorsnackbar. + loggedIn = await model.userLoggedIn(); + expect(loggedIn, true); + // print(model.currentUser); + }); + + test('Test for User log out method.', () async { databaseFunctions.init(); when(databaseFunctions.gqlAuthMutation(queries.logout())) @@ -120,7 +195,7 @@ void main() async { expect(loggedOut, false); }); - test('Test for updateUserJoinedOrg', () async { + test('Test for updateUserJoinedOrg method', () async { final model = UserConfig(); model.currentUser = mockUser; @@ -129,7 +204,7 @@ void main() async { expect(mockUser.joinedOrganizations, mockOrgDetails); }); - test('Test for updateUserCreatedOrg', () async { + test('Test for updateUserCreatedOrg method', () async { final model = UserConfig(); model.currentUser = mockUser; @@ -138,7 +213,7 @@ void main() async { expect(mockUser.createdOrganizations, mockOrgDetails); }); - test('Test for updateUserMemberRequestOrg', () async { + test('Test for updateUserMemberRequestOrg method', () async { final model = UserConfig(); model.currentUser = mockUser; final expected = [...mockUser.membershipRequests!, ...mockOrgDetails]; @@ -147,7 +222,7 @@ void main() async { expect(mockUser.membershipRequests, expected); }); - test('Test for updateUserAdminOrg', () async { + test('Test for updateUserAdminOrg method', () async { final model = UserConfig(); model.currentUser = mockUser; @@ -156,7 +231,7 @@ void main() async { expect(mockUser.adminFor, mockOrgDetails); }); - test('Test for updateAccessToken', () async { + test('Test for updateAccessToken method.', () async { final model = UserConfig(); model.currentUser = mockUser; const newAuthToken = 'newAccessToken'; @@ -170,5 +245,31 @@ void main() async { expect(mockUser.authToken, newAuthToken); expect(mockUser.refreshToken, newRefreshToken); }); + + test('Test for saveCurrentOrgInHive method.', () async { + final model = UserConfig(); + model.currentUser = mockUser; + + // To test the box.get('org') != null condition. + orgBox.put('org', OrgInfo(id: 'fakeId', name: 'org')); + model.saveCurrentOrgInHive(mockOrgDetails[0]); + + // To test the box.get('org') == null condition. + orgBox.delete('org'); + model.saveCurrentOrgInHive(mockOrgDetails[0]); + }); + + test('Test for updateUser method.', () async { + final model = UserConfig(); + + when(databaseFunctions.init()).thenAnswer((_) { + throw Exception('simulated exception.'); + }); + + final updated = await model.updateUser(User(id: 'sampleId')); + + // user updation failed. + expect(!updated, true); + }); }); } diff --git a/test/view_model_tests/access_request_view_model_test.dart b/test/view_model_tests/access_request_view_model_test.dart new file mode 100644 index 0000000000..bd0352028e --- /dev/null +++ b/test/view_model_tests/access_request_view_model_test.dart @@ -0,0 +1,139 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/constants/routing_constants.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/view_model/access_request_view_model.dart'; +import '../helpers/test_helpers.dart'; +import '../helpers/test_locator.dart'; + +class MockCallbackFunction extends Mock { + void call(); +} + +void main() { + testSetupLocator(); + + setUpAll(() { + registerServices(); + }); + + group( + "AccessScreenViewModel Test- ", + () { + test("Check if it's initialized correctly", () { + final org = userConfig.currentOrg; + final model = AccessScreenViewModel(); + expect(model.selectedOrganization.id, '-1'); + model.initialise(org); + expect(model.selectedOrganization.id, 'XYZ'); + }); + + test( + "Check if snackbar is showing in case of not empty joined organization", + () async { + final org = userConfig.currentOrg; + final model = AccessScreenViewModel(); + model.initialise(org); + + when( + databaseFunctions.gqlAuthMutation( + queries.sendMembershipRequest(org.id!), + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions( + document: gql(queries.sendMembershipRequest(org.id!)), + ), + data: { + 'sendMembershipRequest': { + 'organization': { + '_id': 'XYZ', + 'name': 'Organization Name', + 'image': null, + 'description': null, + 'isPublic': false, + 'creator': { + 'firstName': 'ravidi', + 'lastName': 'shaikh', + 'image': null, + }, + }, + }, + }, + source: QueryResultSource.network, + ), + ); + + await model.sendMembershipRequest(); + + verify( + databaseFunctions.gqlAuthMutation( + queries.sendMembershipRequest(org.id!), + ), + ); + + verify(locator().pop()); + verify( + locator().showTalawaErrorSnackBar( + "Join in request sent to Organization Name successfully", + MessageType.info, + ), + ); + }); + + test("Removing joined organizations for testing", () async { + getAndRegisterUserConfig(); + final org = userConfig.currentOrg; + userConfig.currentUser.joinedOrganizations = []; + final model = AccessScreenViewModel(); + model.initialise(org); + + when( + databaseFunctions.gqlAuthMutation( + queries.sendMembershipRequest(org.id!), + ), + ).thenAnswer( + (_) async => QueryResult( + options: QueryOptions( + document: gql(queries.sendMembershipRequest(org.id!)), + ), + data: { + 'sendMembershipRequest': { + 'organization': { + '_id': 'XYZ', + 'name': 'Organization Name', + 'image': null, + 'description': null, + 'isPublic': false, + 'creator': { + 'firstName': 'ravidi', + 'lastName': 'shaikh', + 'image': null, + }, + }, + }, + }, + source: QueryResultSource.network, + ), + ); + + await model.sendMembershipRequest(); + + verify( + databaseFunctions.gqlAuthMutation( + queries.sendMembershipRequest(org.id!), + ), + ); + + verify( + locator().removeAllAndPush( + Routes.waitingScreen, + Routes.splashScreen, + ), + ); + }); + }, + ); +} diff --git a/test/view_model_tests/after_auth_view_model_tests/add_post_view_model_test.dart b/test/view_model_tests/after_auth_view_model_tests/add_post_view_model_test.dart index 1c89d1daba..227eb914e8 100644 --- a/test/view_model_tests/after_auth_view_model_tests/add_post_view_model_test.dart +++ b/test/view_model_tests/after_auth_view_model_tests/add_post_view_model_test.dart @@ -23,6 +23,7 @@ void main() { testSetupLocator(); setUp(() { registerServices(); + getAndRegisterImageService(); }); group("AddPostViewModel Test - ", () { test("Check if it's initialized correctly", () { @@ -36,6 +37,13 @@ void main() { userConfig.currentUser.firstName! + userConfig.currentUser.lastName!, ); }); + + test('Test for imageInBase64 getter', () async { + final model = AddPostViewModel(); + model.initialise(); + expect(model.imageInBase64, null); + }); + test("Check if getImageFromGallery() is working fine", () async { final model = AddPostViewModel(); model.initialise(); @@ -189,25 +197,5 @@ void main() { model.removeImage(); expect(model.imageFile, null); }); - test('convertToBase64 converts file to base64 string', () async { - final notifyListenerCallback = MockCallbackFunction(); - final model = AddPostViewModel()..addListener(notifyListenerCallback); - model.initialise(); - //using this asset as the test asset - final file = File('assets/images/Group 8948.png'); - final fileString = await model.convertToBase64(file); - expect(model.imageInBase64, fileString); - }); - - test( - 'Check if convertToBase64 is working even if wrong file path is provided', - () async { - final notifyListenerCallback = MockCallbackFunction(); - final model = AddPostViewModel()..addListener(notifyListenerCallback); - model.initialise(); - final file = File('fakePath'); - final fileString = await model.convertToBase64(file); - expect('', fileString); - }); }); } diff --git a/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/edit_profile_view_model_test.dart b/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/edit_profile_view_model_test.dart index 112be39daa..d08d21490d 100644 --- a/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/edit_profile_view_model_test.dart +++ b/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/edit_profile_view_model_test.dart @@ -3,67 +3,290 @@ import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:mockito/mockito.dart'; -import 'package:talawa/locator.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/services/size_config.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; import 'package:talawa/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart'; import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; class MockCallbackFunction extends Mock { void call(); } void main() { - setUp(() { + testSetupLocator(); + SizeConfig().test(); + setUpAll(() { registerServices(); + graphqlConfig.test(); + sizeConfig.test(); + }); + + tearDownAll(() { + unregisterServices(); }); group('EditProfilePageViewModel Test -', () { test("Check if it's initialized correctly", () { final model = EditProfilePageViewModel(); model.initialize(); - expect(model.imageFile, null); }); + test('Profile shoud be edited if new values are given', () async { + final model = EditProfilePageViewModel(); + model.initialize(); + final Map mockData = { + 'updateUserProfile': { + '_id': '64378abd85008f171cf2990d', + }, + }; + final String a = await model.convertToBase64(File('path/to/newImage')); + final Map data = { + 'users': [ + { + '_id': '1234567890', + 'firstName': 'John', + 'lastName': 'Doe', + 'email': 'johndoe@example.com', + 'image': 'https://example.com/profile.jpg', + 'accessToken': 'exampleAccessToken', + 'refreshToken': 'exampleRefreshToken', + } + ], + }; + when( + databaseFunctions.gqlAuthMutation( + queries.updateUserProfile(), + variables: { + 'firstName': 'NewFirstName', + 'lastName': 'NewLastName', + 'newImage': 'data:image/png;base64,$a', + }, + ), + ).thenAnswer( + (_) async => QueryResult( + data: mockData, + source: QueryResultSource.network, + options: QueryOptions(document: gql(queries.updateUserProfile())), + ), + ); + when( + databaseFunctions.gqlAuthQuery( + queries.fetchUserInfo, + variables: {'id': model.user.id}, + ), + ).thenAnswer((_) async { + return QueryResult( + source: QueryResultSource.network, + data: data, + options: QueryOptions(document: gql(queries.fetchUserInfo)), + ); + }); + await model.updateUserProfile( + firstName: 'NewFirstName', + lastName: 'NewLastName', + newImage: File('path/to/newImage'), + ); - test( - 'Check if getImageFromGallery() is working fine when no image is return', - () async { - final notifyListenerCallback = MockCallbackFunction(); - final model = EditProfilePageViewModel() - ..addListener(notifyListenerCallback); + verify( + databaseFunctions.gqlAuthMutation( + queries.updateUserProfile(), + variables: { + "firstName": "NewFirstName", + "lastName": "NewLastName", + "file": 'data:image/png;base64,$a', + }, + ), + ).called(1); + verify( + navigationService.showTalawaErrorSnackBar( + "Profile updated successfully", + MessageType.info, + ), + ); + }); - when(locator().getPhotoFromGallery()) + test('Test UpdateUserProfile when throwing exception', () async { + final model = EditProfilePageViewModel(); + model.initialize(); + final String b = await model.convertToBase64(File('path/to/newIma')); + when( + databaseFunctions.gqlAuthMutation( + queries.updateUserProfile(), + variables: { + 'firstName': 'NewFirstNa', + 'lastName': 'NewLastNa', + 'newImage': 'data:image/png;base64,$b', + }, + ), + ).thenThrow(Exception()); + when( + databaseFunctions.gqlAuthQuery( + queries.fetchUserInfo, + variables: {'id': model.user.id}, + ), + ).thenThrow(Exception()); + await model.updateUserProfile( + firstName: 'NewFirstNa', + lastName: 'NewLastNa', + newImage: File('path/to/newIma'), + ); + verify( + navigationService.showTalawaErrorSnackBar( + "Something went wrong", + MessageType.error, + ), + ); + }); + testWidgets('Test if SelectImage from camera method works', + (WidgetTester tester) async { + final model = EditProfilePageViewModel(); + model.initialize(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + key: const Key('btn1'), + onPressed: () => model.selectImage(camera: true), + child: const Text('listner'), + ); + }, + ), + ), + ), + ); + final file = File('fakePath'); + when(locator().getPhotoFromGallery(camera: true)) .thenAnswer((realInvocation) async { - return null; + return file; }); + await tester.tap(find.byKey(const Key('btn1'))); + await tester.pumpAndSettle(); + verify(multimediaPickerService.getPhotoFromGallery(camera: true)) + .called(1); + expect(model.imageFile, file); + }); + testWidgets('Test if selectImage from gallery method works', + (WidgetTester tester) async { + final model = EditProfilePageViewModel(); model.initialize(); - await model.getImageFromGallery(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + key: const Key('btn1'), + onPressed: () => model.selectImage(), + child: const Text('listner'), + ); + }, + ), + ), + ), + ); + final file = File('fakePath'); + when(locator().getPhotoFromGallery()) + .thenAnswer((realInvocation) async { + return file; + }); + await tester.tap(find.byKey(const Key('btn1'))); + await tester.pumpAndSettle(); + expect(model.imageFile, file); + }); + testWidgets( + 'Test if SelectImage from camera method works if null is returned', + (WidgetTester tester) async { + final model = EditProfilePageViewModel(); + model.initialize(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + key: const Key('btn1'), + onPressed: () => model.selectImage(camera: true), + child: const Text('listner'), + ); + }, + ), + ), + ), + ); + when(locator().getPhotoFromGallery(camera: true)) + .thenAnswer((realInvocation) async { + return null; + }); + await tester.tap(find.byKey(const Key('btn1'))); + await tester.pumpAndSettle(); + verify(multimediaPickerService.getPhotoFromGallery(camera: true)) + .called(1); expect(model.imageFile, null); - verifyNever(notifyListenerCallback()); }); - - test('Check if getImageFromGallery() is working fine when iamge is return', - () async { - final notifyListenerCallback = MockCallbackFunction(); - final model = EditProfilePageViewModel() - ..addListener(notifyListenerCallback); - - final file = File('fakePath'); + testWidgets( + 'Test if selectImage from gallery method works when null is returned', + (WidgetTester tester) async { + final model = EditProfilePageViewModel(); + model.initialize(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + key: const Key('btn1'), + onPressed: () => model.selectImage(), + child: const Text('listner'), + ); + }, + ), + ), + ), + ); when(locator().getPhotoFromGallery()) .thenAnswer((realInvocation) async { - return file; + return null; }); + await tester.tap(find.byKey(const Key('btn1'))); + await tester.pumpAndSettle(); + expect(model.imageFile, null); + }); + test('No update performed if inputs are the same as existing data', + () async { + final model = EditProfilePageViewModel(); model.initialize(); - await model.getImageFromGallery(); + await model.updateUserProfile( + firstName: model.user.firstName, + lastName: model.user.lastName, + newImage: null, + ); + verifyNever( + databaseFunctions.gqlAuthMutation( + queries.updateUserProfile(), + variables: {'id': 'xzy1'}, + ), + ); + }); - expect(model.imageFile, file); - verify(notifyListenerCallback()).called(1); + test('convertToBase64 converts file to base64 string', () async { + final model = EditProfilePageViewModel(); + model.initialize(); + //using this asset as the test asset + final file = File('assets/images/Group 8948.png'); + final fileString = await model.convertToBase64(file); + expect(model.base64Image, fileString); }); test('Check if removeImage() is working fine', () async { diff --git a/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/profile_page_view_model_test.dart b/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/profile_page_view_model_test.dart index b4931c9df9..b674342905 100644 --- a/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/profile_page_view_model_test.dart +++ b/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/profile_page_view_model_test.dart @@ -224,7 +224,7 @@ void main() async { ), ), ); - await tester.tap(find.byKey(const Key('dombtn1'))); + await tester.tap(find.byKey(const Key('domBtn_$amt'))); expect(setterCalled, true); final containerFinder = find.byType(Container); final Container container = tester.firstWidget(containerFinder); diff --git a/test/view_model_tests/custom_drawer_view_model_test.dart b/test/view_model_tests/custom_drawer_view_model_test.dart index cb4b695f7e..ee24e31fff 100644 --- a/test/view_model_tests/custom_drawer_view_model_test.dart +++ b/test/view_model_tests/custom_drawer_view_model_test.dart @@ -1,25 +1,38 @@ -// ignore_for_file: talawa_api_doc -// ignore_for_file: talawa_good_doc_comments - import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:talawa/enums/enums.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/models/user/user_info.dart'; import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/size_config.dart'; import 'package:talawa/view_model/main_screen_view_model.dart'; import 'package:talawa/view_model/widgets_view_models/custom_drawer_view_model.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; + import '../helpers/test_helpers.dart'; import '../helpers/test_locator.dart'; +/// Mocked context. +/// +/// more_info_if_required class MockBuildContext extends Mock implements BuildContext {} +/// Main. +/// +/// more_info_if_required +/// +/// **params**: +/// None +/// +/// **returns**: +/// None void main() { - int testCount = 0; testSetupLocator(); locator().test(); locator().test(); + locator(); setUp(() { registerServices(); @@ -29,21 +42,103 @@ void main() { tearDown(() { unregisterServices(); }); + group('CustomDrawerViewModel Tests', () { + test('switchAbleOrg should correctly get and set value', () { + final model = CustomDrawerViewModel(); + final orgList = [ + OrgInfo(name: 'Test Org 1'), + OrgInfo(name: 'Test Org 2'), + ]; + + model.switchAbleOrg = orgList; + + expect(model.switchAbleOrg, equals(orgList)); + }); + test("initialize should setup the model with user's joined organizations", + () { + final homeModel = MainScreenViewModel(); + final MockBuildContext mockContext = MockBuildContext(); + final model = CustomDrawerViewModel(); + final user = User(joinedOrganizations: [OrgInfo(name: 'Test Org')]); + + when(userConfig.currentOrgInfoStream) + .thenAnswer((_) => Stream.value(OrgInfo())); + when(userConfig.currentUser).thenReturn(user); + when(userConfig.currentOrg).thenReturn(OrgInfo()); + + model.initialize(homeModel, mockContext); + + expect(model.switchAbleOrg, equals(user.joinedOrganizations)); + }); + + test('switchOrg should show info message if different organization', () { + final model = CustomDrawerViewModel(); + final orgInfo = OrgInfo(name: 'Test Org'); + + when(userConfig.currentOrg).thenReturn(OrgInfo(name: 'Current Org')); + model.switchAbleOrg = [orgInfo]; + + model.switchOrg(orgInfo); + + verify( + navigationService.showTalawaErrorSnackBar( + 'Switched to ${orgInfo.name}', + MessageType.info, + ), + ); + }); + + test('switchOrg should pop navigation after switching or showing error', + () { + final model = CustomDrawerViewModel(); + final orgInfo = OrgInfo(name: 'Test Org'); + + when(userConfig.currentOrg).thenReturn(OrgInfo(name: 'Current Org')); + model.switchAbleOrg = [orgInfo]; + + model.switchOrg(orgInfo); + + verify(navigationService.pop()); + }); + + test('initialize should setup the model with userConfig values', () { + final homeModel = MainScreenViewModel(); + final MockBuildContext mockContext = MockBuildContext(); + final model = CustomDrawerViewModel(); + final user = User(joinedOrganizations: [OrgInfo(name: 'Test Org')]); + + when(userConfig.currentOrgInfoStream) + .thenAnswer((_) => Stream.value(OrgInfo())); + when(userConfig.currentUser).thenReturn(user); + when(userConfig.currentOrg).thenReturn(OrgInfo()); + + model.initialize(homeModel, mockContext); + + expect(model.switchAbleOrg, equals(user.joinedOrganizations)); + expect(model.selectedOrg, equals(userConfig.currentOrg)); + }); + test( + 'switchOrg should save new organization in userConfig if different organization', + () { + final model = CustomDrawerViewModel(); + final orgInfo = OrgInfo(name: 'Test Org'); - group('Custom Drawer Model testing -', () { - //final mockConnectivity = getAndRegisterConnectivityService(); - final mainscreenModel = MainScreenViewModel(); - final model = CustomDrawerViewModel(); - final MockBuildContext mockBuildContext = MockBuildContext(); - //final UserConfig mockus - tearDown(() { - if (testCount == 5) { - model.dispose(); - } + when(userConfig.currentOrg).thenReturn(OrgInfo(name: 'Current Org')); + model.switchAbleOrg = [orgInfo]; + + model.switchOrg(orgInfo); + + verify(userConfig.saveCurrentOrgInHive(orgInfo)); + verify( + navigationService.showTalawaErrorSnackBar( + 'Switched to ${orgInfo.name}', + MessageType.info, + ), + ); }); test('check if switchOrg is working with zero switchable orgs', () { - print("1"); + final model = CustomDrawerViewModel(); model.setSelectedOrganizationName(userConfig.currentOrg); //No switchable org are present in the model @@ -55,12 +150,11 @@ void main() { //check if selected org is mocked joined org .Expectation-false. expect(model.selectedOrg, isNot(mockJoinedOrg)); - testCount++; }); test('check if switchOrg is working with wrong switchable org being passed', () { - print("2"); + final model = CustomDrawerViewModel(); model.setSelectedOrganizationName(userConfig.currentOrg); //Mock switchable org are present in the model @@ -83,13 +177,14 @@ void main() { expect(isPresent, false); //check if selected org is changed or not. Expected-Not changing expect(model.selectedOrg, isNot(fakeOrg)); - testCount++; }); test('check if switchOrg is working with mock joined orgs', () async { - print("3"); + final model = CustomDrawerViewModel(); + final homeModel = MainScreenViewModel(); + final MockBuildContext mockContext = MockBuildContext(); //Intializing a mock model with mockBuildContext - model.initialize(mainscreenModel, mockBuildContext); + model.initialize(homeModel, mockContext); //Storing the first switchable org in mockOrgInfo final OrgInfo mockChangeOrgTo = model.switchAbleOrg.first; @@ -98,35 +193,92 @@ void main() { //expecting the selected org will be equal to the mockChangeOrgto returns true expect(model.selectedOrg, mockChangeOrgTo); - testCount++; - }); - - // test('check if switchOrg is working with already joined mock orgs', - // () async { - // print("4"); - // //Intializing a mock model with mockBuildContext - // // model.initialize(mainscreenModel, mockBuildContext); - // //Storing the first switchable org in mockOrgInfo - // final OrgInfo mockChangeOrgTo = model.switchAbleOrg.first; - // //Calling the switchOrg function - // model.switchOrg(mockChangeOrgTo); - // model.switchOrg(mockChangeOrgTo); - - // //expecting the selected org will be equal to the mockChangeOrgto returns true - // expect(model.selectedOrg, mockChangeOrgTo); - // testCount++; - // }); - - // test('check if switchOrg is working with switching joined mock orgs', - // () async { - // print("5"); - // // model.initialize(mainscreenModel, mockBuildContext); - // final OrgInfo mockChangeOrgTo = model.switchAbleOrg.first; - // final OrgInfo mockChangeOrgToLast = model.switchAbleOrg.last; - // model.switchOrg(mockChangeOrgTo); - // model.switchOrg(mockChangeOrgToLast); - // expect(model.selectedOrg, mockChangeOrgToLast); - // testCount++; - // }); + }); + + test('setSelectedOrganizationName should update selectedOrg if different', + () { + final model = CustomDrawerViewModel(); + final orgInfo = OrgInfo(name: 'Test Org'); + + model.setSelectedOrganizationName(orgInfo); + + expect(model.selectedOrg, equals(orgInfo)); + }); + + test('Check if OrgInfo is present in switchAbleOrg', () { + final model = CustomDrawerViewModel(); + model.switchAbleOrg = [ + OrgInfo(id: '1'), + OrgInfo(id: '2'), + OrgInfo(id: '3'), + ]; + final switchToOrg = OrgInfo(id: '2'); + + final result = model.isPresentinSwitchableOrg(switchToOrg); + + expect(result, true); + }); + + test('Check if OrgInfo is not present in switchAbleOrg', () { + final model = CustomDrawerViewModel(); + model.switchAbleOrg = [ + OrgInfo(id: '1'), + OrgInfo(id: '2'), + OrgInfo(id: '3'), + ]; + final switchToOrg = OrgInfo(id: '4'); + + final result = model.isPresentinSwitchableOrg(switchToOrg); + + expect(result, false); + }); + + test( + 'setSelectedOrganizationName should show error snackbar if org is same as selected', + () { + final homeModel = MainScreenViewModel(); + final MockBuildContext mockContext = MockBuildContext(); + final model = CustomDrawerViewModel(); + final user = + User(joinedOrganizations: [OrgInfo(id: '1', name: 'Test Org1')]); + + when(userConfig.currentOrgInfoStream) + .thenAnswer((_) => Stream.value(OrgInfo(id: '1', name: 'Test Org1'))); + when(userConfig.currentUser).thenReturn(user); + when(userConfig.currentOrg) + .thenReturn(OrgInfo(id: '1', name: 'Test Org1')); + model.initialize(homeModel, mockContext); + final switchToOrg = OrgInfo(id: '1', name: 'Test Org1'); + model.setSelectedOrganizationName(switchToOrg); + final result1 = model.isPresentinSwitchableOrg(switchToOrg); + + expect(result1, true); + // expect(model.selectedOrg, equals(userConfig.currentOrg)); + model.switchOrg(switchToOrg); + final result = model.isPresentinSwitchableOrg(switchToOrg); + + expect(result, true); + expect(model.selectedOrg, equals(switchToOrg)); + verify( + navigationService.showTalawaErrorSnackBar( + '${switchToOrg.name} already selected', + MessageType.warning, + ), + ).called(1); + }); + test('controller should return ScrollController instance', () { + final model = CustomDrawerViewModel(); + expect(model.controller, isA()); + }); + + test('targets should return List instance', () { + final model = CustomDrawerViewModel(); + expect(model.targets, isA>()); + }); + + test('selectedOrg should be initially null', () { + final model = CustomDrawerViewModel(); + expect(model.selectedOrg, isNull); + }); }); } diff --git a/test/views/after_auth_screens/events/create_event_page_test.dart b/test/views/after_auth_screens/events/create_event_page_test.dart index 1cf338c8bf..1020f44d03 100644 --- a/test/views/after_auth_screens/events/create_event_page_test.dart +++ b/test/views/after_auth_screens/events/create_event_page_test.dart @@ -27,6 +27,14 @@ class MockCallbackFunction extends Mock { final setDateCallback = MockCallbackFunction(); final setTimeCallback = MockCallbackFunction(); +/// Creates a EventScreen for tests. +/// +/// **params**: +/// * `themeMode`: ThemeMode +/// * `theme`: ThemeData of App +/// +/// **returns**: +/// * `Widget`: Event Screen Widget Widget createEventScreen({ ThemeMode themeMode = ThemeMode.light, required ThemeData theme, diff --git a/test/views/after_auth_screens/events/event_filter_bottomsheet_test.dart b/test/views/after_auth_screens/events/event_filter_bottomsheet_test.dart new file mode 100644 index 0000000000..bd75c33020 --- /dev/null +++ b/test/views/after_auth_screens/events/event_filter_bottomsheet_test.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:mocktail_image_network/mocktail_image_network.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; +import 'package:talawa/view_model/main_screen_view_model.dart'; +import 'package:talawa/views/after_auth_screens/events/explore_events.dart'; +import 'package:talawa/widgets/custom_drawer.dart'; + +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; + +class MockCallbackFunction extends Mock { + void call(); +} + +final setDateCallback = MockCallbackFunction(); +final setTimeCallback = MockCallbackFunction(); + +/// Creates Explore Event Screen. +/// +/// **params**: +/// * `model`: Home Screen Model +/// +/// **returns**: +/// * `Widget`: Returns Explore Screen Widget +Widget createExploreEventsScreen(MainScreenViewModel model) => MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + key: const Key('Root'), + home: Scaffold( + key: MainScreenViewModel.scaffoldKey, + drawer: CustomDrawer( + homeModel: model, + ), + body: const ExploreEvents( + key: Key('ExploreEvents'), + ), + ), + navigatorKey: navigationService.navigatorKey, + onGenerateRoute: router.generateRoute, + ); +void main() { + SizeConfig().test(); + testSetupLocator(); + locator.unregister(); + setUp(() { + registerServices(); + }); + tearDown(() { + unregisterServices(); + }); + group('testing filters bottomsheet', () { + testWidgets("Checking tap cross works", (tester) async { + await mockNetworkImages(() async { + locator.unregister(); + final model = ExploreEventsViewModel(); + locator.registerSingleton(model); + final homeModel = locator(); + await tester.pumpWidget(createExploreEventsScreen(homeModel)); + await tester.pumpAndSettle(); + await tester.tap(find.bySemanticsLabel('Filters')); + await tester.pumpAndSettle(); + final finder = find.byKey(const Key('close')); + expect(finder, findsOneWidget); + await tester.tap(finder); + await tester.pumpAndSettle(); + expect(find.bySemanticsLabel('Filters'), findsAtLeast(1)); + }); + }); + testWidgets("Testing if Filter button works", (tester) async { + await mockNetworkImages(() async { + locator.unregister(); + final model = ExploreEventsViewModel(); + locator.registerSingleton(model); + final homeModel = locator(); + await tester.pumpWidget(createExploreEventsScreen(homeModel)); + await tester.pumpAndSettle(); + await tester.tap(find.bySemanticsLabel('Filters')); + await tester.pumpAndSettle(); + await tester.pump(); + await tester.tap(find.byKey(const Key('Public Events'))); + await tester.pumpAndSettle(); + expect(model.chosenValue, 'Public Events'); + }); + }); + }); +} diff --git a/test/views/after_auth_screens/join_org_after_auth_test/access_request_screen_test.dart b/test/views/after_auth_screens/join_org_after_auth_test/access_request_screen_test.dart new file mode 100644 index 0000000000..f061229e0e --- /dev/null +++ b/test/views/after_auth_screens/join_org_after_auth_test/access_request_screen_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/access_request_view_model.dart'; +import 'package:talawa/views/after_auth_screens/join_org_after_auth/access_request_screen.dart'; +import 'package:talawa/views/base_view.dart'; + +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_locator.dart'; + +Widget accessRequestScreen() { + return BaseView( + onModelReady: (model) => model.initialise(fakeOrgInfo), + builder: (context, model, child) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: const [ + AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: SendAccessRequest(org: fakeOrgInfo), + navigatorKey: navigationService.navigatorKey, + ); + }, + ); +} + +void main() { + testSetupLocator(); + setUp(() => registerServices()); + tearDown(() => unregisterServices()); + group("SendRequestAccess Screen test", () { + testWidgets("SendRequestAccess screen is build correctly", + (WidgetTester tester) async { + await tester.pumpWidget(accessRequestScreen()); + await tester.pumpAndSettle(); + + //Verify that appbar is present with transparent background color + expect(find.byType(AppBar), findsOneWidget); + final AppBar appBar = tester.firstWidget(find.byType(AppBar)); + expect(appBar.backgroundColor, Colors.transparent); + + //Verify that the image is present + expect(find.byType(Image), findsOneWidget); + expect(find.text("You need access"), findsOneWidget); + expect( + find.text("Request access, or switch to an account with access"), + findsOneWidget, + ); + expect(find.byType(TextField), findsOneWidget); + //Verify that the send request button is present + expect(find.text("Request Access"), findsOneWidget); + + //Tap the "Request Access" button and trigger a frame + await tester.tap(find.text("Request Access")); + await tester.pump(); + }); + }); +} diff --git a/test/views/after_auth_screens/join_organisation_after_auth_test.dart b/test/views/after_auth_screens/join_org_after_auth_test/join_organisation_after_auth_test.dart similarity index 62% rename from test/views/after_auth_screens/join_organisation_after_auth_test.dart rename to test/views/after_auth_screens/join_org_after_auth_test/join_organisation_after_auth_test.dart index 53d67e89d4..aeb9075374 100644 --- a/test/views/after_auth_screens/join_organisation_after_auth_test.dart +++ b/test/views/after_auth_screens/join_org_after_auth_test/join_organisation_after_auth_test.dart @@ -4,26 +4,31 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:talawa/enums/enums.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/models/user/user_info.dart'; import 'package:talawa/services/graphql_config.dart'; import 'package:talawa/services/size_config.dart'; import 'package:talawa/utils/app_localization.dart'; -import 'package:talawa/view_model/lang_view_model.dart'; import 'package:talawa/view_model/pre_auth_view_models/select_organization_view_model.dart'; import 'package:talawa/views/after_auth_screens/join_org_after_auth/join_organisation_after_auth.dart'; import 'package:talawa/views/base_view.dart'; import 'package:talawa/widgets/organization_search_list.dart'; -import '../../helpers/test_helpers.dart'; -import '../../helpers/test_locator.dart'; +import '../../../helpers/test_helpers.dart'; +import '../../../helpers/test_helpers.mocks.dart'; +import '../../../helpers/test_locator.dart'; -Widget createJoinOrgAfterAuth({String orgId = "fake_id"}) { - return BaseView( - onModelReady: (model) => model.initialize(), - builder: (context, langModel, child) { +Widget createJoinOrgAfterAuth({ + String orgId = "fake_id", +}) { + return BaseView( + onModelReady: (model) => model.initialise(orgId), + builder: (context, model, child) { return MaterialApp( + navigatorKey: navigationService.navigatorKey, locale: const Locale('en'), localizationsDelegates: const [ AppLocalizationsDelegate(isTest: true), @@ -53,6 +58,99 @@ void main() { }); group("Tests for JoinOrganizationAfterAuth - widgets", () { + testWidgets('QR Scan Test', (WidgetTester tester) async { + final controller = MockQRViewController(); + when(controller.scannedDataStream).thenAnswer((_) async* { + yield Barcode( + ' ' + '?orgid=6737904485008f171cf29924', + BarcodeFormat.qrcode, + null, + ); + }); + when(controller.stopCamera()) + .thenAnswer((realInvocation) => Future.value()); + + await tester.pumpWidget( + createJoinOrgAfterAuth(), + ); + + await tester.pumpAndSettle(const Duration(seconds: 6)); + + await tester.tap(find.byIcon(Icons.qr_code_scanner)); + await tester.pumpAndSettle(); + + expect( + find.byWidgetPredicate( + (widget) => + widget is ClipRRect && + widget.child is Container && + (widget.child! as Container).child is Column, + ), + findsOneWidget, + ); + (tester.widget(find.byType(QRView)) as QRView) + .onQRViewCreated(controller); + }); + testWidgets('QR Scan Test when url != GraphqlConfig.orgURI', + (WidgetTester tester) async { + final controller = MockQRViewController(); + when(controller.scannedDataStream).thenAnswer((_) async* { + yield Barcode( + '1' + '?orgid=6737904485008f171cf29924', + BarcodeFormat.qrcode, + null, + ); + }); + when(controller.stopCamera()) + .thenAnswer((realInvocation) => Future.value()); + + await tester.pumpWidget( + createJoinOrgAfterAuth(), + ); + + await tester.pumpAndSettle(const Duration(seconds: 6)); + + await tester.tap(find.byIcon(Icons.qr_code_scanner)); + await tester.pumpAndSettle(); + + expect( + find.byWidgetPredicate( + (widget) => + widget is ClipRRect && + widget.child is Container && + (widget.child! as Container).child is Column, + ), + findsOneWidget, + ); + (tester.widget(find.byType(QRView)) as QRView) + .onQRViewCreated(controller); + }); + testWidgets('Test _onQRViewCreated when throwing exception', + (WidgetTester tester) async { + final controller = MockQRViewController(); + when(controller.scannedDataStream).thenAnswer((_) async* { + yield Barcode( + ' ' + '?orgid=6737904485008f171cf29924', + BarcodeFormat.qrcode, + null, + ); + }); + when(controller.stopCamera()) + .thenAnswer((realInvocation) => Future.value()); + + await tester.pumpWidget( + createJoinOrgAfterAuth(), + ); + when(controller.stopCamera()).thenThrow(Exception("exception")); + + await tester.pumpAndSettle(const Duration(seconds: 6)); + + await tester.tap(find.byIcon(Icons.qr_code_scanner)); + await tester.pumpAndSettle(); + + (tester.widget(find.byType(QRView)) as QRView) + .onQRViewCreated(controller); + }); testWidgets( "Check if JoinOrganizationsAfterAuth shows up", (tester) async { @@ -132,13 +230,6 @@ void main() { /// Search is No-Longer is a feature, if it gets implemented in future use this test /// Really good test to learn from so not deleting testWidgets("Check if model related functions work", (tester) async { - // Registers a singleton, which means that every instance of - // SelectOrganizationViewModel will be the same. - locator.unregister(); - locator.registerSingleton( - SelectOrganizationViewModel(), - ); - final orgOne = OrgInfo( name: "org_one", creatorInfo: User( diff --git a/test/views/after_auth_screens/profile/profile_page_test.dart b/test/views/after_auth_screens/profile/profile_page_test.dart index 8a4e4861b9..64a7ced120 100644 --- a/test/views/after_auth_screens/profile/profile_page_test.dart +++ b/test/views/after_auth_screens/profile/profile_page_test.dart @@ -59,8 +59,6 @@ void main() async { await Hive.openBox('currentOrg'); final pbox = await Hive.openBox('pluginBox'); print(pbox.get('plugins')); - // locator.unregister(); - // locator.registerFactory(() => ProfilePageViewModel()); }); tearDownAll(() { @@ -69,14 +67,75 @@ void main() async { File('test/fixtures/core/currentuser.hive').delete(); File('test/fixtures/core/currentuser.lock').delete(); }); - testWidgets('check if profilePage shows up', (tester) async { - // print(); + testWidgets('check if profilePage shows up and refreshIndicator work', + (tester) async { await tester.pumpWidget( createProfilePage( mainScreenViewModel: locator(), ), ); + await tester.pump(); + expect(find.byType(RefreshIndicator), findsOneWidget); + await tester.drag( + find.byKey(const Key('profilepic')), + const Offset(0, 300), + ); + await tester.pumpAndSettle(); + }); + testWidgets('check if invitebutton work', (tester) async { + await tester.pumpWidget( + createProfilePage( + mainScreenViewModel: locator(), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key('inviteicon'))); + await tester.pumpAndSettle(); + }); + testWidgets('check if Donate button work', (tester) async { + await tester.pumpWidget( + createProfilePage( + mainScreenViewModel: locator(), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('Donate to the Community')); + await tester.pumpAndSettle(); + }); + testWidgets('check if naviagte to task screen work', (tester) async { + await tester.pumpWidget( + createProfilePage( + mainScreenViewModel: locator(), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('Tasks')); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key('tastscrn'))); + await tester.pumpAndSettle(); + }); + testWidgets('check if Invite customListTile work', (tester) async { + await tester.pumpWidget( + createProfilePage( + mainScreenViewModel: locator(), + ), + ); + await tester.pumpAndSettle(); + await tester.ensureVisible(find.text('Invite')); + await tester.tap(find.text('Invite')); + await tester.pumpAndSettle(); + }); + testWidgets('check if modal sheet for settings shows up', (tester) async { + await tester.pumpWidget( + createProfilePage( + mainScreenViewModel: locator(), + ), + ); + await tester.pumpAndSettle(); + await tester.ensureVisible(find.byKey(const Key('settingIcon'))); + await tester.tap(find.byKey(const Key('settingIcon'))); await tester.pumpAndSettle(); + expect(find.byKey(const Key('sheetContainer')), findsOneWidget); }); }); } diff --git a/test/widget_tests/after_auth_screens/add_post_page_test.dart b/test/widget_tests/after_auth_screens/add_post_page_test.dart index f9f15117c2..2d6d3c9e1c 100644 --- a/test/widget_tests/after_auth_screens/add_post_page_test.dart +++ b/test/widget_tests/after_auth_screens/add_post_page_test.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:talawa/locator.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart'; @@ -14,7 +15,6 @@ import 'package:talawa/view_model/main_screen_view_model.dart'; import 'package:talawa/views/after_auth_screens/add_post_page.dart'; import '../../helpers/test_helpers.dart'; -import '../../helpers/test_locator.dart'; final homeModel = locator(); bool removeImageCalled = false; @@ -32,6 +32,9 @@ class MockAddPostViewModel extends Mock implements AddPostViewModel { @override String get userName => 'UserName'; + @override + String? get userPic => userConfig.currentUser.image; + @override String get orgName => 'orgName'; @@ -86,11 +89,13 @@ Widget createAddPostScreen({ void main() { // SizeConfig().test(); - testSetupLocator(); + setupLocator(); // locator.registerSingleton(LikeButtonViewModel()); + sizeConfig.test(); setUp(() { registerServices(); + getAndRegisterImageService(); }); group('createAddPostScreen Test', () { @@ -273,7 +278,7 @@ void main() { }); await tester.tap(finder); - await tester.pump(); + await tester.pumpAndSettle(); await tester.tap(cancelBtn); await tester.pump(); diff --git a/test/widget_tests/after_auth_screens/events/explore_events_test.dart b/test/widget_tests/after_auth_screens/events/explore_events_test.dart index d227dc2ecc..2d37da6cee 100644 --- a/test/widget_tests/after_auth_screens/events/explore_events_test.dart +++ b/test/widget_tests/after_auth_screens/events/explore_events_test.dart @@ -140,13 +140,12 @@ void main() { await tester.pumpWidget(createExploreEventsScreen(homeModel)); await tester.pumpAndSettle(); - await tester.tap(find.byType(DropdownButtonHideUnderline)); + await tester.tap(find.bySemanticsLabel('Filters')); await tester.pumpAndSettle(); - - await tester.tap(find.bySemanticsLabel('Created Events')); + await tester.pump(); + await tester.tap(find.byKey(const Key('Public Events'))); await tester.pumpAndSettle(); - - expect(model.chosenValue, 'Created Events'); + expect(model.chosenValue, 'Public Events'); }); }); testWidgets("Testing if tapping on calendar works", (tester) async { @@ -156,7 +155,7 @@ void main() { await tester.pumpWidget(createExploreEventsScreen(homeModel)); await tester.pumpAndSettle(); - await tester.tap(find.text('Add Date')); + await tester.tap(find.text('Filter by Date')); await tester.pump(); expect(find.byType(ExploreEventDialog), findsOneWidget); diff --git a/test/widget_tests/after_auth_screens/feed/pinned_post_page_test.dart b/test/widget_tests/after_auth_screens/feed/pinned_post_page_test.dart index e2ee03f619..4d0b3957a9 100644 --- a/test/widget_tests/after_auth_screens/feed/pinned_post_page_test.dart +++ b/test/widget_tests/after_auth_screens/feed/pinned_post_page_test.dart @@ -11,6 +11,9 @@ import 'package:talawa/router.dart' as router; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/views/after_auth_screens/feed/pinned_post_page.dart'; +import 'package:talawa/widgets/post_list_widget.dart'; +import 'package:talawa/widgets/post_widget.dart'; +import 'package:visibility_detector/visibility_detector.dart'; import '../../../helpers/test_helpers.dart'; @@ -44,14 +47,16 @@ void main() { }); group('Tests for pinned post page', () { - // testWidgets('Check whether PinnedPostPage shows up', (tester) async { - // VisibilityDetectorController.instance.updateInterval = Duration.zero; - // - // await tester.pumpWidget(createPinnedPostPage()); - // await tester.pump(); - // - // expect(find.byType(PinnedPostPage), findsOneWidget); - // expect(find.byType(PostListWidget), findsOneWidget); - // }); + testWidgets('Check whether PinnedPostPage shows up', (tester) async { + VisibilityDetectorController.instance.updateInterval = Duration.zero; + + await tester.pumpWidget(createPinnedPostPage()); + await tester.pump(); + + expect(find.byType(PinnedPostPage), findsOneWidget); + expect(find.byType(PostListWidget), findsOneWidget); + expect(find.byType(NewsPost), findsOneWidget); + expect(find.textContaining('firstName1'), findsOneWidget); + }); }); } diff --git a/test/widget_tests/after_auth_screens/profile/edit_profile_page_test.dart b/test/widget_tests/after_auth_screens/profile/edit_profile_page_test.dart index 24698a81ae..fb32846060 100644 --- a/test/widget_tests/after_auth_screens/profile/edit_profile_page_test.dart +++ b/test/widget_tests/after_auth_screens/profile/edit_profile_page_test.dart @@ -27,6 +27,9 @@ import '../../../helpers/test_locator.dart'; class MockBuildContext extends Mock implements BuildContext {} +class MockEditProfilePageViewModel extends Mock + implements EditProfilePageViewModel {} + class MockCallbackFunction extends Mock { void call(); } @@ -129,7 +132,7 @@ Future main() async { TalawaTheme.lightTheme.scaffoldBackgroundColor, ); final imageWidgetWithPicture = find.byKey( - const Key('UserImageNotInDb'), + const Key('profilepic'), ); expect(imageWidgetWithPicture, findsOneWidget); }); @@ -160,7 +163,7 @@ Future main() async { TalawaTheme.lightTheme.scaffoldBackgroundColor, ); final imageWidgetWithPicture = find.byKey( - const Key('UserImageInDb'), + const Key('profilepic'), ); expect(imageWidgetWithPicture, findsOneWidget); }); @@ -213,12 +216,20 @@ Future main() async { ); expect(appBarText, findsOneWidget); }); - testWidgets("Testing if Edit Screen shows image when not exist in database", + const Key('profilepic'); + testWidgets( + "Testing if Edit Screen shows image when already exist in database", (tester) async { await mockNetworkImages(() async { userConfig.updateUser(User()); + userConfig.updateUser( - User(firstName: 'Test', lastName: 'Test', email: 'test@test.com'), + User( + firstName: 'Test', + lastName: 'Test', + email: 'test@test.com', + image: 'https://via.placeholder.com/150', + ), ); await tester.pumpWidget(createChangePassScreenDark()); await tester.pumpAndSettle(); @@ -233,13 +244,12 @@ Future main() async { TalawaTheme.darkTheme.scaffoldBackgroundColor, ); final imageWidgetWithPicture = find.byKey( - const Key('UserImageNotInDb'), + const Key('profilepic'), ); expect(imageWidgetWithPicture, findsOneWidget); }); }); - testWidgets( - "Testing if Edit Screen shows image when already exist in database", + testWidgets("Testing if modalSheet appears when changing profile picture", (tester) async { await mockNetworkImages(() async { userConfig.updateUser(User()); @@ -264,10 +274,84 @@ Future main() async { .scaffoldBackgroundColor, TalawaTheme.darkTheme.scaffoldBackgroundColor, ); - final imageWidgetWithPicture = find.byKey( - const Key('UserImageInDb'), + await tester.tap(find.byKey(const Key('AddRemoveImageButton'))); + await tester.pumpAndSettle(); + expect(find.text('Camera'), findsOneWidget); + expect(find.text('Gallery'), findsOneWidget); + expect(find.byIcon(Icons.camera_alt), findsOneWidget); + expect(find.byIcon(Icons.photo_library), findsOneWidget); + }); + }); + testWidgets("Testing if image selection from camera work fine", + (tester) async { + await mockNetworkImages(() async { + userConfig.updateUser(User()); + + userConfig.updateUser( + User( + firstName: 'Test', + lastName: 'Test', + email: 'test@test.com', + image: 'https://via.placeholder.com/150', + ), ); - expect(imageWidgetWithPicture, findsOneWidget); + await tester.pumpWidget(createChangePassScreenDark()); + await tester.pumpAndSettle(); + final screenScaffoldWidget = find.byKey( + const Key('EditProfileScreenScaffold'), + ); + expect(screenScaffoldWidget, findsOneWidget); + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .theme! + .scaffoldBackgroundColor, + TalawaTheme.darkTheme.scaffoldBackgroundColor, + ); + await tester.tap(find.byKey(const Key('AddRemoveImageButton'))); + await tester.pumpAndSettle(); + expect(find.text('Camera'), findsOneWidget); + expect(find.text('Gallery'), findsOneWidget); + expect(find.byIcon(Icons.camera_alt), findsOneWidget); + expect(find.byIcon(Icons.photo_library), findsOneWidget); + + await tester.ensureVisible(find.byIcon(Icons.camera_alt)); + await tester.tap(find.byIcon(Icons.camera_alt)); + }); + }); + testWidgets("Testing if image selection from gallery work fine", + (tester) async { + await mockNetworkImages(() async { + userConfig.updateUser(User()); + + userConfig.updateUser( + User( + firstName: 'Test', + lastName: 'Test', + email: 'test@test.com', + image: 'https://via.placeholder.com/150', + ), + ); + await tester.pumpWidget(createChangePassScreenDark()); + await tester.pumpAndSettle(); + final screenScaffoldWidget = find.byKey( + const Key('EditProfileScreenScaffold'), + ); + expect(screenScaffoldWidget, findsOneWidget); + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .theme! + .scaffoldBackgroundColor, + TalawaTheme.darkTheme.scaffoldBackgroundColor, + ); + await tester.tap(find.byKey(const Key('AddRemoveImageButton'))); + await tester.pumpAndSettle(); + expect(find.text('Camera'), findsOneWidget); + expect(find.text('Gallery'), findsOneWidget); + expect(find.byIcon(Icons.camera_alt), findsOneWidget); + expect(find.byIcon(Icons.photo_library), findsOneWidget); + + await tester.ensureVisible(find.byIcon(Icons.photo_library)); + await tester.tap(find.byIcon(Icons.photo_library)); }); }); testWidgets("Testing if image selection and removal works", (tester) async { @@ -295,6 +379,30 @@ Future main() async { tester.tap(imageAvatar); }); }); + testWidgets("Testing Update butoon", (tester) async { + await mockNetworkImages(() async { + userConfig.updateUser(User()); + userConfig.updateUser( + User(firstName: 'Test', lastName: 'Test', email: 'test@test.com'), + ); + await tester.pumpWidget(createChangePassScreenDark()); + await tester.pumpAndSettle(); + final screenScaffoldWidget = find.byKey( + const Key('EditProfileScreenScaffold'), + ); + expect(screenScaffoldWidget, findsOneWidget); + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .theme! + .scaffoldBackgroundColor, + TalawaTheme.darkTheme.scaffoldBackgroundColor, + ); + final updateButtonFinder = find.byKey(const Key('updatebtn')); + expect(updateButtonFinder, findsOneWidget); + await tester.tap(updateButtonFinder); + await tester.pumpAndSettle(); + }); + }); }); group('Testing image selection and removal in Edit Profile Screen', () { setUp(() { @@ -305,34 +413,39 @@ Future main() async { tearDown(() { unregisterServices(); }); - testWidgets('Testing image selection and removal in Edit Profile Screen', + + testWidgets( + 'Testing image selection when user is selecting image from device', (tester) async { final notifyListenerCallback = MockCallbackFunction(); final model = EditProfilePageViewModel() ..addListener(notifyListenerCallback); model.initialize(); - // testing getImageFromGallery - // with camera false - when(multimediaPickerService.getPhotoFromGallery(camera: false)) - .thenAnswer((realInvocation) async { - return null; - }); - - await model.getImageFromGallery(); - verify(multimediaPickerService.getPhotoFromGallery(camera: false)); - expect(model.imageFile, null); - // with camera true final file = File('fakePath'); when(multimediaPickerService.getPhotoFromGallery(camera: true)) .thenAnswer((_) async { return file; }); - await model.getImageFromGallery(camera: true); + await model.getImage(camera: true); verify(multimediaPickerService.getPhotoFromGallery(camera: true)); expect(model.imageFile, file); verify(notifyListenerCallback()); + await tester.pumpWidget(createChangePassScreenDark()); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('AddRemoveImageButton')), findsOneWidget); + await tester.tap(find.byKey(const Key('AddRemoveImageButton'))); + await tester.pumpAndSettle(); + await tester.tap(find.byIcon(Icons.camera_alt)); + await tester.pumpAndSettle(); + expect(model.imageFile, isNotNull); + }); + testWidgets('Testing if image removal work properly', (tester) async { + final notifyListenerCallback = MockCallbackFunction(); + final model = EditProfilePageViewModel() + ..addListener(notifyListenerCallback); + model.initialize(); // testing removeImage model.removeImage(); diff --git a/test/widget_tests/widgets/lang_switch_test.dart b/test/widget_tests/widgets/lang_switch_test.dart index 9c920cbbdc..02b6be38fe 100644 --- a/test/widget_tests/widgets/lang_switch_test.dart +++ b/test/widget_tests/widgets/lang_switch_test.dart @@ -16,7 +16,7 @@ import '../../helpers/test_locator.dart'; Widget createLanguageTile() { return BaseView( - onModelReady: (model) => model.initialize(), + onModelReady: (appLanguageModel) => appLanguageModel.initialize(), builder: (_, __, ___) => MaterialApp( localizationsDelegates: [ const AppLocalizationsDelegate(isTest: true), diff --git a/test/widget_tests/widgets/post_widget_test.dart b/test/widget_tests/widgets/post_widget_test.dart index 15d0af208d..20f2669281 100644 --- a/test/widget_tests/widgets/post_widget_test.dart +++ b/test/widget_tests/widgets/post_widget_test.dart @@ -273,7 +273,7 @@ void main() { // Testing props of Custom Avatar Widget expect(customAvatarWidget.isImageNull, true); - expect(customAvatarWidget.imageUrl, null); + expect(customAvatarWidget.imageUrl, ' /null'); expect(customAvatarWidget.fontSize, 20); expect(customAvatarWidget.firstAlphabet, 'T');