diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 97a56b04..48d4975f 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -91,7 +91,7 @@ jobs: - run: flutter pub get - name: Prepare web run: ./scripts/prepare-web.sh - - run: flutter build web --dart-define=WITH_SEMANTICS=true + - run: flutter build web - name: Upload Web Build uses: actions/upload-artifact@v7 with: @@ -140,7 +140,7 @@ jobs: integration_test: runs-on: ubuntu-latest timeout-minutes: 60 - needs: [ build_debug_apk ] + needs: [ code_tests ] strategy: matrix: api-level: [34] @@ -151,10 +151,6 @@ jobs: AVD_CONFIG_PATH: "~/.android/avd/test.avd/config.ini" steps: - uses: actions/checkout@v6 - - uses: actions/download-artifact@v8 - with: - name: debug-apk-x64 - path: . - uses: ./.github/actions/free_up_space # https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ - name: Enable KVM group perms @@ -200,26 +196,20 @@ jobs: with: flutter-version-file: .tool_versions.yaml cache: true - - uses: remarkablemark/setup-maestro-cli@v1 - - name: Load integration test env - run: cat integration_test/data/integration_users.env >> $GITHUB_ENV + - uses: moonrepo/setup-rust@v1 + with: + cache: true + - name: Cache Gradle + uses: actions/cache@v5 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: gradle-${{ runner.os }}- + - run: ./scripts/add-firebase-messaging.sh - name: Prepare Homeserver - run: | - docker run -d --name synapse --tmpfs /data \ - --volume="$(pwd)/integration_test/synapse/data/homeserver.yaml":/data/homeserver.yaml:rw \ - --volume="$(pwd)/integration_test/synapse/data/localhost.log.config":/data/localhost.log.config:rw \ - -p 80:80 matrixdotorg/synapse:latest - while ! curl -XGET "http://$HOMESERVER/_matrix/client/v3/login" >/dev/null 2>/dev/null; do - echo "Waiting for homeserver to be available... (GET http://$HOMESERVER/_matrix/client/v3/login)" - sleep 2 - done - - echo "Homeserver is online!" - - # create users - curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER1_NAME\", \"password\":\"$USER1_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://$HOMESERVER/_matrix/client/r0/register" - curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER2_NAME\", \"password\":\"$USER2_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://$HOMESERVER/_matrix/client/r0/register" - + run: ./scripts/prepare_integration_test.sh - name: Integration tests id: integration_tests uses: reactivecircus/android-emulator-runner@v2 @@ -234,16 +224,4 @@ jobs: ram-size: 4096M sdcard-path-or-size: 4096M emulator-options: -no-snapshot-save -no-window -wipe-data -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - script: | - flutter run --use-application-binary=$PWD/app-debug.apk > flutter_logs.txt 2>&1 & - FLUTTER_PID=$! - maestro test integration_test/login.yaml --env HOMESERVER=10.0.2.2 --env USER1_NAME=${USER1_NAME} --env USER1_PW=${USER1_PW} - kill $FLUTTER_PID 2>/dev/null || true - cp flutter_logs.txt ~/.maestro/tests/ - - name: Upload Flutter and Maestro logs - if: failure() - uses: actions/upload-artifact@v7 - with: - name: maestro-logs - path: ~/.maestro/tests - if-no-files-found: ignore \ No newline at end of file + script: flutter test integration_test/mobile_test.dart \ No newline at end of file diff --git a/README.md b/README.md index b118f085..50fccbfc 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,20 @@ flutter build windows --release flutter build macos --release ``` +## How to run integration tests + +You need to have docker installed locally! Run the preparation script before every test run: + +```sh +./scripts/prepare_integration_test.sh +``` + +Then run all tests with: + +```sh +flutter test integration_test/mobile_test.dart +``` + # Special thanks diff --git a/integration_test/data/environment_constants.dart b/integration_test/data/environment_constants.dart new file mode 100644 index 00000000..57e8f20a --- /dev/null +++ b/integration_test/data/environment_constants.dart @@ -0,0 +1,14 @@ +const homeserver = String.fromEnvironment( + 'HOMESERVER', + defaultValue: '10.0.2.2', +); +const user1Name = String.fromEnvironment('USER1_NAME', defaultValue: 'alice'); +const user1Pw = String.fromEnvironment( + 'USER1_PW', + defaultValue: 'AliceInWonderland', +); +const user2Name = String.fromEnvironment('USER2_NAME', defaultValue: 'bob'); +const user2Pw = String.fromEnvironment( + 'USER2_PW', + defaultValue: 'JoWirSchaffenDas', +); diff --git a/integration_test/dendrite/data/dendrite.yaml b/integration_test/dendrite/data/dendrite.yaml deleted file mode 100644 index f1e95fd4..00000000 --- a/integration_test/dendrite/data/dendrite.yaml +++ /dev/null @@ -1,327 +0,0 @@ -# This is the Dendrite configuration file. -# -# The configuration is split up into sections - each Dendrite component has a -# configuration section, in addition to the "global" section which applies to -# all components. - -# The version of the configuration file. -version: 2 - -# Global Matrix configuration. This configuration applies to all components. -global: - # The domain name of this homeserver. - server_name: localhost - - # The path to the signing private key file, used to sign requests and events. - # Note that this is NOT the same private key as used for TLS! To generate a - # signing key, use "./bin/generate-keys --private-key matrix_key.pem". - private_key: matrix_key.pem - - # The paths and expiry timestamps (as a UNIX timestamp in millisecond precision) - # to old signing private keys that were formerly in use on this domain. These - # keys will not be used for federation request or event signing, but will be - # provided to any other homeserver that asks when trying to verify old events. - old_private_keys: - # - private_key: old_matrix_key.pem - # expired_at: 1601024554498 - - # How long a remote server can cache our server signing key before requesting it - # again. Increasing this number will reduce the number of requests made by other - # servers for our key but increases the period that a compromised key will be - # considered valid by other homeservers. - key_validity_period: 168h0m0s - - # Global database connection pool, for PostgreSQL monolith deployments only. If - # this section is populated then you can omit the "database" blocks in all other - # sections. For polylith deployments, or monolith deployments using SQLite databases, - # you must configure the "database" block for each component instead. - database: - connection_string: - max_open_conns: - max_idle_conns: - conn_max_lifetime: - - # Configuration for in-memory caches. Caches can often improve performance by - # keeping frequently accessed items (like events, identifiers etc.) in memory - # rather than having to read them from the database. - cache: - # The estimated maximum size for the global cache in bytes, or in terabytes, - # gigabytes, megabytes or kilobytes when the appropriate 'tb', 'gb', 'mb' or - # 'kb' suffix is specified. Note that this is not a hard limit, nor is it a - # memory limit for the entire process. A cache that is too small may ultimately - # provide little or no benefit. - max_size_estimated: 1gb - - # The maximum amount of time that a cache entry can live for in memory before - # it will be evicted and/or refreshed from the database. Lower values result in - # easier admission of new cache entries but may also increase database load in - # comparison to higher values, so adjust conservatively. Higher values may make - # it harder for new items to make it into the cache, e.g. if new rooms suddenly - # become popular. - max_age: 1h - - # The server name to delegate server-server communications to, with optional port - # e.g. localhost:443 - well_known_server_name: "" - - # Lists of domains that the server will trust as identity servers to verify third - # party identifiers such as phone numbers and email addresses. - trusted_third_party_id_servers: - - matrix.org - - vector.im - - # Disables federation. Dendrite will not be able to communicate with other servers - # in the Matrix federation and the federation API will not be exposed. - disable_federation: false - - # Configures the handling of presence events. Inbound controls whether we receive - # presence events from other servers, outbound controls whether we send presence - # events for our local users to other servers. - presence: - enable_inbound: false - enable_outbound: false - - # Configures phone-home statistics reporting. These statistics contain the server - # name, number of active users and some information on your deployment config. - # We use this information to understand how Dendrite is being used in the wild. - report_stats: - enabled: false - endpoint: https://matrix.org/report-usage-stats/push - - # Server notices allows server admins to send messages to all users on the server. - server_notices: - enabled: false - # The local part, display name and avatar URL (as a mxc:// URL) for the user that - # will send the server notices. These are visible to all users on the deployment. - local_part: "_server" - display_name: "Server Alerts" - avatar_url: "" - # The room name to be used when sending server notices. This room name will - # appear in user clients. - room_name: "Server Alerts" - - # Configuration for NATS JetStream - jetstream: - # A list of NATS Server addresses to connect to. If none are specified, an - # internal NATS server will be started automatically when running Dendrite in - # monolith mode. For polylith deployments, it is required to specify the address - # of at least one NATS Server node. - addresses: - # - localhost:4222 - - # Persistent directory to store JetStream streams in. This directory should be - # preserved across Dendrite restarts. - storage_path: ./ - - # The prefix to use for stream names for this homeserver - really only useful - # if you are running more than one Dendrite server on the same NATS deployment. - topic_prefix: Dendrite - - # Configuration for Prometheus metric collection. - metrics: - enabled: false - basic_auth: - username: metrics - password: metrics - - # Optional DNS cache. The DNS cache may reduce the load on DNS servers if there - # is no local caching resolver available for use. - dns_cache: - enabled: false - cache_size: 256 - cache_lifetime: "5m" # 5 minutes; https://pkg.go.dev/time@master#ParseDuration - -# Configuration for the Appservice API. -app_service_api: - database: - connection_string: file:app_service_api.db - - # Disable the validation of TLS certificates of appservices. This is - # not recommended in production since it may allow appservice traffic - # to be sent to an insecure endpoint. - disable_tls_validation: true - - # Appservice configuration files to load into this homeserver. - config_files: - # - /path/to/appservice_registration.yaml - -# Configuration for the Client API. -client_api: - # Prevents new users from being able to register on this homeserver, except when - # using the registration shared secret below. - registration_disabled: false - - # Prevents new guest accounts from being created. Guest registration is also - # disabled implicitly by setting 'registration_disabled' above. - guests_disabled: true - - # If set, allows registration by anyone who knows the shared secret, regardless - # of whether registration is otherwise disabled. - registration_shared_secret: "" - - # Whether to require reCAPTCHA for registration. If you have enabled registration - # then this is HIGHLY RECOMMENDED to reduce the risk of your homeserver being used - # for coordinated spam attacks. - enable_registration_captcha: false - - # Settings for ReCAPTCHA. - recaptcha_public_key: "" - recaptcha_private_key: "" - recaptcha_bypass_secret: "" - recaptcha_siteverify_api: "" - - # TURN server information that this homeserver should send to clients. - turn: - turn_user_lifetime: "" - turn_uris: - # - turn:turn.server.org?transport=udp - # - turn:turn.server.org?transport=tcp - turn_shared_secret: "" - turn_username: "" - turn_password: "" - - # Settings for rate-limited endpoints. Rate limiting kicks in after the threshold - # number of "slots" have been taken by requests from a specific host. Each "slot" - # will be released after the cooloff time in milliseconds. Server administrators - # and appservice users are exempt from rate limiting by default. - rate_limiting: - enabled: true - threshold: 5 - cooloff_ms: 500 - exempt_user_ids: - # - "@user:domain.com" - -# Configuration for the Federation API. -federation_api: - database: - connection_string: file:federation_api.db - - # How many times we will try to resend a failed transaction to a specific server. The - # backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc. Once - # the max retries are exceeded, Dendrite will no longer try to send transactions to - # that server until it comes back to life and connects to us again. - send_max_retries: 16 - - # Disable the validation of TLS certificates of remote federated homeservers. Do not - # enable this option in production as it presents a security risk! - disable_tls_validation: false - - # Perspective keyservers to use as a backup when direct key fetches fail. This may - # be required to satisfy key requests for servers that are no longer online when - # joining some rooms. - key_perspectives: - - server_name: matrix.org - keys: - - key_id: ed25519:auto - public_key: Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw - - key_id: ed25519:a_RXGa - public_key: l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ - - # This option will control whether Dendrite will prefer to look up keys directly - # or whether it should try perspective servers first, using direct fetches as a - # last resort. - prefer_direct_fetch: false - -# Configuration for the Media API. -media_api: - database: - connection_string: file:media_api.db - - # Storage path for uploaded media. May be relative or absolute. - base_path: ./media_store - - # The maximum allowed file size (in bytes) for media uploads to this homeserver - # (0 = unlimited). If using a reverse proxy, ensure it allows requests at least - #this large (e.g. the client_max_body_size setting in nginx). - max_file_size_bytes: 10485760 - - # Whether to dynamically generate thumbnails if needed. - dynamic_thumbnails: false - - # The maximum number of simultaneous thumbnail generators to run. - max_thumbnail_generators: 10 - - # A list of thumbnail sizes to be generated for media content. - thumbnail_sizes: - - width: 32 - height: 32 - method: crop - - width: 96 - height: 96 - method: crop - - width: 640 - height: 480 - method: scale - -# Configuration for enabling experimental MSCs on this homeserver. -mscs: - database: - connection_string: file:mscs.db - mscs: - # - msc2836 # (Threading, see https://github.com/matrix-org/matrix-doc/pull/2836) - # - msc2946 # (Spaces Summary, see https://github.com/matrix-org/matrix-doc/pull/2946) - -# Configuration for the Sync API. -sync_api: - # This option controls which HTTP header to inspect to find the real remote IP - # address of the client. This is likely required if Dendrite is running behind - # a reverse proxy server. - # real_ip_header: X-Real-IP - database: - connection_string: file:sync_api.db - -key_server: - database: - connection_string: file:key_server.db - -room_server: - database: - connection_string: file:room_server.db - - -# Configuration for the User API. -user_api: - account_database: - connection_string: file:user_api.db - - # The cost when hashing passwords on registration/login. Default: 10. Min: 4, Max: 31 - # See https://pkg.go.dev/golang.org/x/crypto/bcrypt for more information. - # Setting this lower makes registration/login consume less CPU resources at the cost - # of security should the database be compromised. Setting this higher makes registration/login - # consume more CPU resources but makes it harder to brute force password hashes. This value - # can be lowered if performing tests or on embedded Dendrite instances (e.g WASM builds). - bcrypt_cost: 10 - - # The length of time that a token issued for a relying party from - # /_matrix/client/r0/user/{userId}/openid/request_token endpoint - # is considered to be valid in milliseconds. - # The default lifetime is 3600000ms (60 minutes). - # openid_token_lifetime_ms: 3600000 - -# Configuration for Opentracing. -# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on -# how this works and how to set it up. -tracing: - enabled: false - jaeger: - serviceName: "" - disabled: false - rpc_metrics: false - tags: [] - sampler: null - reporter: null - headers: null - baggage_restrictions: null - throttler: null - -# Logging configuration. The "std" logging type controls the logs being sent to -# stdout. The "file" logging type controls logs being written to a log folder on -# the disk. Supported log levels are "debug", "info", "warn", "error". -logging: - - type: std - level: info - - type: file - level: info - params: - path: ./logs - diff --git a/integration_test/flows/auth_flows.dart b/integration_test/flows/auth_flows.dart new file mode 100644 index 00000000..e16fd686 --- /dev/null +++ b/integration_test/flows/auth_flows.dart @@ -0,0 +1,44 @@ +import 'package:fluffychat/pages/sign_in/view_model/model/public_homeserver_data.dart'; +import 'package:flutter/material.dart'; + +import '../data/environment_constants.dart'; +import '../utils/fluffy_chat_tester.dart'; + +extension AuthFlows on FluffyChatTester { + Future login() async { + await waitFor('Sign in'); + await tapOn('Sign in'); + await enterText(TextField, 'http://$homeserver', index: 0); + await tapOn(RadioListTile, index: 0); + await tapOn('Continue'); + await waitFor('Log in to http://$homeserver'); + await enterText(TextField, user1Name, index: 0); + await enterText(TextField, user1Pw, index: 1); + await tapOn('Login'); + } + + Future logout() async { + await tapOn(Key('accounts_and_settings_buttons')); + await tapOn('Settings'); + await scrollUntilVisible('Logout'); + await tapOn('Logout'); + await tapOn(Key('ok_cancel_alert_dialog_ok_button')); + await waitFor('Sign in'); + } + + Future skipNoNotificationsDialog() async { + if (await isVisible('Push notifications not available')) { + await tapOn('Do not show again'); + } + } + + Future ensureLoggedIn() async { + if (await isVisible('Sign in') == false) return false; + + await login(); + await tapOn(CloseButton); + await tapOn('Skip'); + await skipNoNotificationsDialog(); + return true; + } +} diff --git a/integration_test/flows/basic_messaging.dart b/integration_test/flows/basic_messaging.dart new file mode 100644 index 00000000..eb3e0944 --- /dev/null +++ b/integration_test/flows/basic_messaging.dart @@ -0,0 +1,42 @@ +import 'package:fluffychat/pages/chat_list/chat_list.dart'; +import 'package:fluffychat/widgets/chat_settings_popup_menu.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/fluffy_chat_tester.dart'; +import 'auth_flows.dart'; + +Future basicMessaging(WidgetTester widgetTester) => widgetTester + .startFluffyChatTest() + .then((tester) => tester._basicMessaging()); + +extension on FluffyChatTester { + Future _basicMessaging() async { + final shouldLogout = await ensureLoggedIn(); + + // Create a new group chat + await tapOn(FloatingActionButton); + await tapOn('Create group'); + await enterText(TextField, 'Test Group 01'); + await tapOn('Create a group and invite users'); + await waitFor('Invite contact'); + await goBack(); + + // Send a message + const testMessage = 'Hello from integration test!'; + await enterText(Key('chat_input_field'), testMessage); + await tapOn(Key('send_button')); + + // Ensure message is visible + await waitFor(testMessage); + + // Archive the chat + await tapOn(ChatSettingsPopupMenu); + await tapOn('Leave'); + await waitFor('Are you sure?'); + await tapOn('Leave'); + await waitFor(ChatList); + + if (shouldLogout) await logout(); + } +} diff --git a/integration_test/flows/login_and_chat_backup.dart b/integration_test/flows/login_and_chat_backup.dart new file mode 100644 index 00000000..b588c444 --- /dev/null +++ b/integration_test/flows/login_and_chat_backup.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; + +import '../utils/fluffy_chat_tester.dart'; +import 'auth_flows.dart'; + +Future loginAndChatBackup(WidgetTester widgetTester) => widgetTester + .startFluffyChatTest() + .then((tester) => tester._loginAndChatBackup()); + +extension on FluffyChatTester { + Future _loginAndChatBackup() async { + await login(); + + // Skip bootstrap + await tapOn('Copy to clipboard'); + await tapOn('Next'); + await tapOn('Close'); + + await skipNoNotificationsDialog(); + + await logout(); + } +} diff --git a/integration_test/login.yaml b/integration_test/login.yaml deleted file mode 100644 index 5bd1a3bd..00000000 --- a/integration_test/login.yaml +++ /dev/null @@ -1,36 +0,0 @@ -appId: chat.fluffy.fluffychat ---- -- extendedWaitUntil: # Wait for app to be visible - visible: "Sign in" - timeout: 60000 -- retry: - maxRetries: 10 # Emulator might need some time to be ready to - commands: - - tapOn: "Sign in" -- tapOn: "Search or enter homeserver address" -- inputText: "http://${HOMESERVER}" -- pressKey: "back" -- tapOn: - id: "homeserver_tile_0" -- tapOn: - id: "connect_to_homeserver_button" -- assertVisible: "Log in to http://${HOMESERVER}" -- inputText: "${USER1_NAME}" -- tapOn: "Password" -- inputText: "${USER1_PW}" -- tapOn: "Login" # Click the login button -- tapOn: - id: "store_in_secure_storage" -- tapOn: "Next" -- tapOn: - text: "Close" - index: 1 -- assertVisible: "Push notifications not available" -- tapOn: "Do not show again" -- tapOn: - id: "accounts_and_settings" # Open the popup menu -- tapOn: "Settings" -- scrollUntilVisible: - element: "Logout" -- tapOn: "Logout" -- tapOn: "Logout" # Confirm logout dialog \ No newline at end of file diff --git a/integration_test/mobile_test.dart b/integration_test/mobile_test.dart new file mode 100644 index 00000000..fe10b949 --- /dev/null +++ b/integration_test/mobile_test.dart @@ -0,0 +1,14 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'flows/basic_messaging.dart'; +import 'flows/login_and_chat_backup.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('FluffyChat Integration Tests', () { + testWidgets('Login and logout flow', loginAndChatBackup); + testWidgets('Basic Messaging', basicMessaging); + }); +} diff --git a/integration_test/utils/fluffy_chat_tester.dart b/integration_test/utils/fluffy_chat_tester.dart new file mode 100644 index 00000000..fe3f754a --- /dev/null +++ b/integration_test/utils/fluffy_chat_tester.dart @@ -0,0 +1,117 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fluffychat/main.dart' as app; + +extension type FluffyChatTester(WidgetTester tester) { + static int _printCounter = 1; + + void _print(String message) { + debugPrint( + '[INTEGRATION TEST] ${DateTime.now().toIso8601String()} | Step ${_printCounter++} - $message', + ); + } + + Future isVisible( + Object object, { + Duration timeout = const Duration(seconds: 3), + }) async { + final end = DateTime.now().add(timeout); + while (object.toFinder().evaluate().isEmpty) { + if (DateTime.now().isAfter(end)) { + return false; + } + await tester.pump(const Duration(milliseconds: 500)); + } + return true; + } + + Future waitFor( + Object object, { + Duration timeout = const Duration(seconds: 30), + }) async { + _print('👀 Waiting for "$object" (${timeout.inSeconds}s)'); + final end = DateTime.now().add(timeout); + while (object.toFinder().evaluate().isEmpty) { + if (DateTime.now().isAfter(end)) { + throw Exception('⏱️ Timed out waiting for "$object"!'); + } + await tester.pump(const Duration(milliseconds: 500)); + } + } + + Future _ensureVisible(Object object, {int? index}) async { + var finder = object.toFinder(); + if (finder.evaluate().isEmpty) await waitFor(object); + + if (finder.evaluate().length > 1) { + if (index == null) { + throw Exception( + '⚠️ Found ${finder.evaluate().length} "$object" objects. Please specify an index!', + ); + } + if (finder.evaluate().length <= index) { + throw Exception( + '⚠️ Found ${finder.evaluate().length} "$object" objects. So index $index does not exist!', + ); + } + finder = finder.at(index); + } + return finder; + } + + Future tapOn(Object object, {int? index}) async { + final finder = await _ensureVisible(object, index: index); + + _print('👆 Tapping on "$object"'); + await tester.tap(finder); + await tester.pumpAndSettle(); + } + + Future goBack() async { + _print('🔙 Going a page back'); + await tester.pageBack(); + } + + Future enterText(Object object, String text, {int? index}) async { + final finder = await _ensureVisible(object, index: index); + + _print('⌨️ Enter "$text" into "$object"'); + await tester.enterText(finder, text); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + } + + Future scrollUntilVisible( + Object object, { + int? index, + Object? scrollable, + }) async { + _print('📜 Scrolling to "$object"'); + await tester.scrollUntilVisible( + object.toFinder(), + 500.0, + scrollable: scrollable?.toFinder() ?? find.byType(Scrollable).last, + ); + await tester.pumpAndSettle(); + } +} + +extension on Object { + Finder toFinder() => switch (this) { + final Finder finder => finder, + final String string => find.text(string), + final Key key => find.byKey(key), + final IconData icon => find.byIcon(icon), + final Type type => find.byType(type), + final Widget widget => find.byWidget(widget), + _ => throw Exception('Unsupported finder type: $runtimeType'), + }; +} + +extension StartTest on WidgetTester { + Future startFluffyChatTest() async { + app.main(); + + return FluffyChatTester(this); + } +} diff --git a/lib/main.dart b/lib/main.dart index 12bd01e1..4817f1df 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,7 +3,6 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/semantics.dart'; import 'package:collection/collection.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -22,6 +21,8 @@ import 'widgets/fluffy_chat_app.dart'; ReceivePort? mainIsolateReceivePort; +bool _vodozemacInitialized = false; + void main() async { if (PlatformInfos.isAndroid) { final port = mainIsolateReceivePort = ReceivePort(); @@ -49,7 +50,10 @@ void main() async { final store = await AppSettings.init(); Logs().i('Welcome to ${AppSettings.applicationName.value} <3'); - await vod.init(wasmPath: './assets/assets/vodozemac/'); + if (!_vodozemacInitialized) { + await vod.init(wasmPath: './assets/assets/vodozemac/'); + _vodozemacInitialized = true; + } Logs().nativeColors = !PlatformInfos.isIOS; final clients = await ClientManager.getClients(store: store); @@ -103,9 +107,6 @@ Future startGui(List clients, SharedPreferences store) async { await firstClient?.accountDataLoading; runApp(FluffyChatApp(clients: clients, pincode: pin, store: store)); - if (const String.fromEnvironment('WITH_SEMANTICS') == 'true') { - SemanticsBinding.instance.ensureSemantics(); - } } /// Watches the lifecycle changes to start the application when it diff --git a/lib/pages/bootstrap/bootstrap_dialog.dart b/lib/pages/bootstrap/bootstrap_dialog.dart index 88d30813..2be97eed 100644 --- a/lib/pages/bootstrap/bootstrap_dialog.dart +++ b/lib/pages/bootstrap/bootstrap_dialog.dart @@ -204,39 +204,31 @@ class BootstrapDialogState extends State { ), const SizedBox(height: 16), if (_supportsSecureStorage) - Semantics( - identifier: 'store_in_secure_storage', - child: CheckboxListTile.adaptive( - contentPadding: const EdgeInsets.symmetric( - horizontal: 8.0, - ), - value: _storeInSecureStorage, - activeColor: theme.colorScheme.primary, - onChanged: (b) { - setState(() { - _storeInSecureStorage = b; - }); - }, - title: Text(_getSecureStorageLocalizedName()), - subtitle: Text( - L10n.of(context).storeInSecureStorageDescription, - ), + CheckboxListTile.adaptive( + contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), + value: _storeInSecureStorage, + activeColor: theme.colorScheme.primary, + onChanged: (b) { + setState(() { + _storeInSecureStorage = b; + }); + }, + title: Text(_getSecureStorageLocalizedName()), + subtitle: Text( + L10n.of(context).storeInSecureStorageDescription, ), ), const SizedBox(height: 16), - Semantics( - identifier: 'copy_to_clipboard', - child: CheckboxListTile.adaptive( - contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), - value: _recoveryKeyCopied, - activeColor: theme.colorScheme.primary, - onChanged: (b) { - FluffyShare.share(key!, context); - setState(() => _recoveryKeyCopied = true); - }, - title: Text(L10n.of(context).copyToClipboard), - subtitle: Text(L10n.of(context).saveKeyManuallyDescription), - ), + CheckboxListTile.adaptive( + contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), + value: _recoveryKeyCopied, + activeColor: theme.colorScheme.primary, + onChanged: (b) { + FluffyShare.share(key!, context, copyOnly: true); + setState(() => _recoveryKeyCopied = true); + }, + title: Text(L10n.of(context).copyToClipboard), + subtitle: Text(L10n.of(context).saveKeyManuallyDescription), ), const SizedBox(height: 16), ElevatedButton.icon( diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index ae774ca7..e1bee846 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -382,6 +382,7 @@ class ChatInputRow extends StatelessWidget { ), ) : IconButton( + key: Key('send_button'), tooltip: L10n.of(context).send, onPressed: controller.send, style: IconButton.styleFrom( diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index bc1261ff..22becb30 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -385,6 +385,7 @@ class InputBar extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); return Autocomplete>( + key: Key('chat_input_field'), focusNode: focusNode, textEditingController: controller, optionsBuilder: getSuggestions, diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 8cde34e1..fdf196d8 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -173,23 +173,20 @@ class ClientChooserButton extends StatelessWidget { clipBehavior: Clip.hardEdge, borderRadius: BorderRadius.circular(99), color: Colors.transparent, - child: Semantics( - identifier: 'accounts_and_settings', - child: PopupMenuButton( - tooltip: 'Accounts and settings', - popUpAnimationStyle: FluffyThemes.isColumnMode(context) - ? AnimationStyle.noAnimation - : null, // https://github.com/flutter/flutter/issues/167180 - onSelected: (o) => _clientSelected(o, context), - itemBuilder: _bundleMenuItems, - child: Center( - child: Avatar( - mxContent: snapshot.data?.avatarUrl, - name: - snapshot.data?.displayName ?? - matrix.client.userID?.localpart, - size: 32, - ), + child: PopupMenuButton( + key: Key('accounts_and_settings_buttons'), + tooltip: 'Accounts and settings', + popUpAnimationStyle: FluffyThemes.isColumnMode(context) + ? AnimationStyle.noAnimation + : null, // https://github.com/flutter/flutter/issues/167180 + onSelected: (o) => _clientSelected(o, context), + itemBuilder: _bundleMenuItems, + child: Center( + child: Avatar( + mxContent: snapshot.data?.avatarUrl, + name: + snapshot.data?.displayName ?? matrix.client.userID?.localpart, + size: 32, ), ), ), diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 2611af78..4f5be9a8 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -79,7 +79,6 @@ class SettingsController extends State { context: context, future: () => matrix.client.logout(), ); - context.go('/'); } Future setAvatarAction() async { diff --git a/lib/pages/sign_in/sign_in_page.dart b/lib/pages/sign_in/sign_in_page.dart index 242ee57e..276cd264 100644 --- a/lib/pages/sign_in/sign_in_page.dart +++ b/lib/pages/sign_in/sign_in_page.dart @@ -96,104 +96,98 @@ class SignInPage extends StatelessWidget { itemBuilder: (context, i) { final server = publicHomeservers[i]; final website = server.website; - return Semantics( - identifier: 'homeserver_tile_$i', - child: RadioListTile( - value: server, - enabled: - state.loginLoading.connectionState != - ConnectionState.waiting, - title: Row( - children: [ - Expanded( - child: Text(server.name ?? 'Unknown'), - ), - if (website != null) - SizedBox.square( - dimension: 32, - child: IconButton( - icon: const Icon( - Icons.open_in_new_outlined, - size: 16, - ), - onPressed: () => - launchUrlString(website), + return RadioListTile( + value: server, + enabled: + state.loginLoading.connectionState != + ConnectionState.waiting, + title: Row( + children: [ + Expanded( + child: Text(server.name ?? 'Unknown'), + ), + if (website != null) + SizedBox.square( + dimension: 32, + child: IconButton( + icon: const Icon( + Icons.open_in_new_outlined, + size: 16, ), + onPressed: () => + launchUrlString(website), ), - ], - ), - subtitle: Column( - spacing: 4.0, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (server.features?.isNotEmpty == true) - Wrap( - spacing: 4.0, - runSpacing: 4.0, - children: [ - ...?server.languages?.map( - (language) => Material( - borderRadius: - BorderRadius.circular( - AppConfig.borderRadius, - ), - color: theme - .colorScheme - .tertiaryContainer, - child: Padding( - padding: - const EdgeInsets.symmetric( - horizontal: 6.0, - vertical: 3.0, - ), - child: Text( - language, - style: TextStyle( - fontSize: 10, - color: theme - .colorScheme - .onTertiaryContainer, - ), - ), - ), - ), - ), - ...server.features!.map( - (feature) => Material( - borderRadius: - BorderRadius.circular( - AppConfig.borderRadius, - ), - color: theme - .colorScheme - .secondaryContainer, - child: Padding( - padding: - const EdgeInsets.symmetric( - horizontal: 6.0, - vertical: 3.0, - ), - child: Text( - feature, - style: TextStyle( - fontSize: 10, - color: theme - .colorScheme - .onSecondaryContainer, - ), - ), - ), - ), - ), - ], - ), - Text( - server.description ?? - 'A matrix homeserver', ), - ], - ), + ], + ), + subtitle: Column( + spacing: 4.0, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (server.features?.isNotEmpty == true) + Wrap( + spacing: 4.0, + runSpacing: 4.0, + children: [ + ...?server.languages?.map( + (language) => Material( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + color: theme + .colorScheme + .tertiaryContainer, + child: Padding( + padding: + const EdgeInsets.symmetric( + horizontal: 6.0, + vertical: 3.0, + ), + child: Text( + language, + style: TextStyle( + fontSize: 10, + color: theme + .colorScheme + .onTertiaryContainer, + ), + ), + ), + ), + ), + ...server.features!.map( + (feature) => Material( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + color: theme + .colorScheme + .secondaryContainer, + child: Padding( + padding: + const EdgeInsets.symmetric( + horizontal: 6.0, + vertical: 3.0, + ), + child: Text( + feature, + style: TextStyle( + fontSize: 10, + color: theme + .colorScheme + .onSecondaryContainer, + ), + ), + ), + ), + ), + ], + ), + Text( + server.description ?? 'A matrix homeserver', + ), + ], ), ); }, @@ -217,29 +211,26 @@ class SignInPage extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(16.0), child: SafeArea( - child: Semantics( - identifier: 'connect_to_homeserver_button', - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.primary, - foregroundColor: theme.colorScheme.onPrimary, - ), - onPressed: - state.loginLoading.connectionState == - ConnectionState.waiting - ? null - : () => connectToHomeserverFlow( - selectedHomserver, - context, - viewModel.setLoginLoading, - signUp, - ), - child: - state.loginLoading.connectionState == - ConnectionState.waiting - ? const CircularProgressIndicator.adaptive() - : Text(L10n.of(context).continueText), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, ), + onPressed: + state.loginLoading.connectionState == + ConnectionState.waiting + ? null + : () => connectToHomeserverFlow( + selectedHomserver, + context, + viewModel.setLoginLoading, + signUp, + ), + child: + state.loginLoading.connectionState == + ConnectionState.waiting + ? const CircularProgressIndicator.adaptive() + : Text(L10n.of(context).continueText), ), ), ), diff --git a/lib/utils/fluffy_share.dart b/lib/utils/fluffy_share.dart index c2bd2a5d..0c72d284 100644 --- a/lib/utils/fluffy_share.dart +++ b/lib/utils/fluffy_share.dart @@ -24,9 +24,14 @@ abstract class FluffyShare { return; } await Clipboard.setData(ClipboardData(text: text)); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(L10n.of(context).copiedToClipboard))); + if (!PlatformInfos.isMobile) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + showCloseIcon: true, + content: Text(L10n.of(context).copiedToClipboard), + ), + ); + } return; } diff --git a/lib/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart b/lib/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart index 3b6c7268..d295a9ca 100644 --- a/lib/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart +++ b/lib/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart @@ -41,11 +41,13 @@ Future showOkCancelAlertDialog({ ), actions: [ AdaptiveDialogAction( + key: Key('ok_cancel_alert_dialog_cancel_button'), onPressed: () => Navigator.of(context).pop(OkCancelResult.cancel), child: Text(cancelLabel ?? L10n.of(context).cancel), ), AdaptiveDialogAction( + key: Key('ok_cancel_alert_dialog_ok_button'), onPressed: () => Navigator.of(context).pop(OkCancelResult.ok), autofocus: true, diff --git a/lib/widgets/future_loading_dialog.dart b/lib/widgets/future_loading_dialog.dart index cc6b1b40..b883cda9 100644 --- a/lib/widgets/future_loading_dialog.dart +++ b/lib/widgets/future_loading_dialog.dart @@ -85,7 +85,11 @@ class LoadingDialogState extends State { void initState() { super.initState(); widget.future.then( - (result) => Navigator.of(context).pop>(Result.value(result)), + (result) { + if (!mounted) return; + if (!Navigator.of(context).canPop()) return; + Navigator.of(context).pop>(Result.value(result)); + }, onError: (e, s) => setState(() { exception = e; stackTrace = s; diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 0e322e10..f5cd50a2 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -371,7 +371,6 @@ class MatrixState extends State with WidgetsBindingObserver { onKeyVerificationRequestSub.values.map((s) => s.cancel()); onLogoutSub.values.map((s) => s.cancel()); onNotification.values.map((s) => s.cancel()); - client.httpClient.close(); linuxNotifications?.close(); diff --git a/pubspec.lock b/pubspec.lock index 08fd79c7..0158ec42 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -462,6 +462,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_foreground_task: dependency: "direct main" description: @@ -669,6 +674,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" geoclue: dependency: transitive description: @@ -901,6 +911,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.6.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: "direct main" description: @@ -1738,6 +1753,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" synchronized: dependency: transitive description: @@ -2099,6 +2122,14 @@ packages: url: "https://github.com/google/webcrypto.dart.git" source: git version: "0.6.0" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + url: "https://pub.dev" + source: hosted + version: "3.1.0" webkit_inspection_protocol: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6776ec92..d83c3eb3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -87,6 +87,8 @@ dev_dependencies: flutter_test: sdk: flutter import_sorter: ^4.6.0 + integration_test: + sdk: flutter license_checker: ^1.6.2 translations_cleaner: ^0.1.1 diff --git a/scripts/create_fdroid_repos.sh b/scripts/create_fdroid_repos.sh deleted file mode 100755 index 595e1cfa..00000000 --- a/scripts/create_fdroid_repos.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash - -GITLAB_PROJECT_ID="16112282" - -# repo directory for build -mkdir fdroid/repo -# ... and for deployment -mkdir repo - -git fetch - -# building nightly repo - -cd fdroid - -cp config.nightly.py config.py - -PIPELINES="$(curl https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines\?ref=main\&status=success\&order_by=updated_at | jq '.[].id' | head -n3)" - -cp ../build/android/app-release.apk repo/fluffychat-latest.apk - -for PIPELINE in $PIPELINES -do - JOB="$(curl https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines/$PIPELINE/jobs | jq -r '.[] | select(.name == "build_android_apk").id')" - if [ -n $JOB ]; then - URI="https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/jobs/$JOB/artifacts/build/android/app-release.apk" - FILENAME="fluffychat-$PIPELINE.apk" - echo "Downloading $FILENAME from $URI ..." - wget --output-document="$FILENAME" "$URI" - mv "$FILENAME" repo - fi -done - -fdroid update --rename-apks -mkdir /fdroid && fdroid deploy -rm -rf /fdroid/archive -cd .. && mv -v /fdroid repo/nightly - -# building stable + RC repo - -rm -rf /fdroid fdroid/repo - -mkdir fdroid/repo - -cd fdroid -rm -f repo/*.apk - -cp config.stable.py config.py - -PIPELINES="$(curl https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines\?scope=tags\&status=success\&order_by=updated_at | jq '.[].id' | head -n3)" - -for PIPELINE in $PIPELINES -do - JOB="$(curl https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines/$PIPELINE/jobs | jq -r '.[] | select(.name == "build_android_apk").id')" - if [ -n $JOB ]; then - URI="https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/jobs/$JOB/artifacts/build/android/app-release.apk" - FILENAME="fluffychat-$PIPELINE.apk" - echo "Downloading $FILENAME from $URI ..." - wget --output-document="$FILENAME" "$URI" - mv "$FILENAME" repo - fi -done - -fdroid update --rename-apks -mkdir /fdroid && fdroid deploy -rm -rf /fdroid/archive -cd .. && mv -v /fdroid repo/stable diff --git a/scripts/integration-check-release-build.sh b/scripts/integration-check-release-build.sh deleted file mode 100755 index 5e329189..00000000 --- a/scripts/integration-check-release-build.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# generate a temporary signing key adn apply its configuration -cd android -KEYFILE="$(pwd)/key.jks" -echo "Generating signing configuration with $KEYFILE..." -keytool -genkey -keyalg RSA -alias key -keysize 4096 -dname "cn=FluffyChat CI, ou=Head of bad integration tests, o=FluffyChat HQ, c=TLH" -keypass FLUFFYCHAT -storepass FLUFFYCHAT -validity 1 -keystore "$KEYFILE" -storetype "pkcs12" -echo "storePassword=FLUFFYCHAT" >> key.properties -echo "keyPassword=FLUFFYCHAT" >> key.properties -echo "keyAlias=key" >> key.properties -echo "storeFile=$KEYFILE" >> key.properties -ls | grep key -cd .. - -# build release mode APK -flutter pub get -flutter build apk --release - -# install and launch APK -flutter install -adb shell am start -n chat.fluffy.fluffychat/chat.fluffy.fluffychat.MainActivity - -sleep 5 - -# check whether FluffyChat runs -adb shell ps | awk '{print $9}' | grep chat.fluffy.fluffychat diff --git a/scripts/integration-prepare-homeserver.sh b/scripts/integration-prepare-homeserver.sh deleted file mode 100755 index 61c43e07..00000000 --- a/scripts/integration-prepare-homeserver.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - - -while ! curl -XGET "http://localhost/_matrix/client/v3/login" >/dev/null 2>/dev/null; do - echo "Waiting for homeserver to be available... (GET http://localhost/_matrix/client/v3/login)" - sleep 2 -done - -# create users -curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER1_NAME\", \"password\":\"$USER1_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://localhost/_matrix/client/r0/register" -curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER2_NAME\", \"password\":\"$USER2_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://localhost/_matrix/client/r0/register" diff --git a/scripts/integration-prepare-host.sh b/scripts/integration-prepare-host.sh deleted file mode 100755 index f110aab7..00000000 --- a/scripts/integration-prepare-host.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -if ! command -v apk &>/dev/null; then - apt update && apt install -y -qq docker.io ldnsutils grep scrcpy ffmpeg -else - apk update && apk add docker drill grep scrcpy ffmpeg -fi diff --git a/scripts/integration-server-conduit.sh b/scripts/integration-server-conduit.sh deleted file mode 100755 index f4b031f5..00000000 --- a/scripts/integration-server-conduit.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -docker run -d \ - -e CONDUIT_SERVER_NAME="localhost" \ - -e CONDUIT_PORT="8008" \ - -e CONDUIT_DATABASE_BACKEND="rocksdb" \ - -e CONDUIT_ALLOW_REGISTRATION=true \ - -e CONDUIT_ALLOW_FEDERATION=true \ - -e CONDUIT_MAX_REQUEST_SIZE="20000000" \ - -e CONDUIT_TRUSTED_SERVERS="[\"conduit.rs\"]" \ - -e CONDUIT_MAX_CONCURRENT_REQUESTS="100" \ - -e CONDUIT_LOG="info,rocket=off,_=off,sled=off" \ - --name conduit -p 80:8008 matrixconduit/matrix-conduit:latest diff --git a/scripts/integration-server-dendrite.sh b/scripts/integration-server-dendrite.sh deleted file mode 100755 index ee23da19..00000000 --- a/scripts/integration-server-dendrite.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -chown -R 991:991 integration_test/dendrite - -# creating integration test SSL certificates -docker run --rm --entrypoint="" \ - --volume="$(pwd)/integration_test/dendrite/data":/mnt:rw \ - matrixdotorg/dendrite-monolith:latest \ - /usr/bin/generate-keys \ - -private-key /mnt/matrix_key.pem \ - -tls-cert /mnt/server.crt \ - -tls-key /mnt/server.key - -docker run -d --volume="$(pwd)/integration_test/dendrite/data":/etc/dendrite:rw \ - --name dendrite -p 80:8008 matrixdotorg/dendrite-monolith:latest -really-enable-open-registration diff --git a/scripts/integration-server-synapse.sh b/scripts/integration-server-synapse.sh deleted file mode 100755 index 03d21847..00000000 --- a/scripts/integration-server-synapse.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -docker run -d --name synapse --tmpfs /data \ - --volume="$(pwd)/integration_test/synapse/data/homeserver.yaml":/data/homeserver.yaml:rw \ - --volume="$(pwd)/integration_test/synapse/data/localhost.log.config":/data/localhost.log.config:rw \ - -p 80:80 matrixdotorg/synapse:latest diff --git a/scripts/integration-start-avd.sh b/scripts/integration-start-avd.sh deleted file mode 100755 index fb608e44..00000000 --- a/scripts/integration-start-avd.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -chmod 777 -R /dev/kvm -adb start-server -emulator -avd test -wipe-data -no-audio -no-boot-anim -no-window -accel on -gpu swiftshader_indirect diff --git a/scripts/prepare-fdroid.sh b/scripts/prepare-fdroid.sh deleted file mode 100755 index f9384b0f..00000000 --- a/scripts/prepare-fdroid.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -cp -r android/fastlane fdroid/metadata/chat.fluffy.fluffychat -cd fdroid -echo $FDROID_KEY | base64 --decode --ignore-garbage > key.jks -echo $FDROID_NIGHTLY_KEY | base64 --decode --ignore-garbage > key.nightly.jks -echo "keypass=\"${FDROID_KEY_PASS}\"" >> config.stable.py -echo "keystorepass=\"${FDROID_KEY_PASS}\"" >> config.stable.py -echo "keypass=\"${FDROID_NIGHTLY_KEY_PASS}\"" >> config.nightly.py -echo "keystorepass=\"${FDROID_NIGHTLY_KEY_PASS}\"" >> config.nightly.py -chmod 600 config.stable.py key.jks config.nightly.py key.nightly.jks -cd .. diff --git a/scripts/prepare_integration_test.sh b/scripts/prepare_integration_test.sh new file mode 100755 index 00000000..e1eb01eb --- /dev/null +++ b/scripts/prepare_integration_test.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +source integration_test/data/integration_users.env + +docker rm -f synapse 2>/dev/null || true + +docker run -d --name synapse --tmpfs /data \ + --volume="$(pwd)/integration_test/synapse/data/homeserver.yaml":/data/homeserver.yaml:rw \ + --volume="$(pwd)/integration_test/synapse/data/localhost.log.config":/data/localhost.log.config:rw \ + -p 80:80 matrixdotorg/synapse:latest + +while ! curl -XGET "http://$HOMESERVER/_matrix/client/v3/login" >/dev/null 2>/dev/null; do +echo "Waiting for homeserver to be available... (GET http://$HOMESERVER/_matrix/client/v3/login)" + sleep 2 +done + +echo "Homeserver is online!" + +# create users +curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER1_NAME\", \"password\":\"$USER1_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://$HOMESERVER/_matrix/client/r0/register" +curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER2_NAME\", \"password\":\"$USER2_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://$HOMESERVER/_matrix/client/r0/register" + +echo "Homeserver is ready! Please rerun this script every time before starting an integration test!" \ No newline at end of file diff --git a/scripts/update-dependencies.sh b/scripts/update-dependencies.sh deleted file mode 100755 index 5f783fc5..00000000 --- a/scripts/update-dependencies.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -ve -flutter pub upgrade --major-versions -flutter pub get -dart fix --apply -flutter format lib test -flutter pub run import_sorter:main --no-comments \ No newline at end of file