feat: patrol on-device integration smoke test

This commit is contained in:
randogoth 2026-09-28 23:49:39 +03:00
parent d6f4c434fa
commit 6f6fa20b58
10 changed files with 466 additions and 6 deletions

11
.gitignore vendored
View file

@ -42,3 +42,14 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
/android/build
# Rust wrapper crate (native/)
native/target/
# Rust cdylibs built into the APK by devbox run build-native-android
android/app/src/main/jniLibs/
# Patrol generates the bundle from the integration tests at run time
**/test_bundle.dart
.patrol.env

5
android/.gitignore vendored
View file

@ -1,8 +1,9 @@
gradle-wrapper.jar gradle-wrapper.jar
/.gradle /.gradle
/captures/ /captures/
/gradlew # gradlew stays tracked: it carries the nix project-cache redirect that every
/gradlew.bat # non-flutter gradle invocation (patrol_cli, manual builds) needs, so the
# wrapper can't be re-injected from the template.
/local.properties /local.properties
GeneratedPluginRegistrant.java GeneratedPluginRegistrant.java

View file

@ -25,6 +25,25 @@ android {
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
// The Android Test Orchestrator (with clearPackageData) wipes the
// integration flavor's app data between test runs, and the flavor
// installs it beside — never over — the real app, so every patrol run
// starts at onboarding with a fresh identity.
testInstrumentationRunner = "pl.leancode.patrol.PatrolJUnitRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
flavorDimensions += "test"
productFlavors {
create("integration") {
dimension = "test"
applicationIdSuffix = ".integration"
versionNameSuffix = "-integration"
}
} }
buildTypes { buildTypes {
@ -38,3 +57,10 @@ android {
flutter { flutter {
source = "../.." source = "../.."
} }
dependencies {
// The orchestrator referenced by testOptions.execution above. The patrol
// native library itself comes from the patrol pub package's android
// module (a plugin subproject), not from a Maven artifact.
androidTestUtil("androidx.test:orchestrator:1.5.1")
}

View file

@ -0,0 +1,38 @@
package com.app.smol_mail.integration;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import pl.leancode.patrol.PatrolJUnitRunner;
// The Patrol host class: each Dart test in the integration_test directory is
// executed through this JUnit suite. Its package is the flavored applicationId
// (com.app.smol_mail + .integration suffix); the namespace differs, so
// MainActivity needs the explicit import below.
@RunWith(Parameterized.class)
public class MainActivityTest {
@Parameters(name = "{0}")
public static Object[] testCases() {
PatrolJUnitRunner instrumentation =
(PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation();
instrumentation.setUp(com.example.smol_mail.MainActivity.class);
instrumentation.waitForPatrolAppService();
return instrumentation.listDartTests();
}
public MainActivityTest(String dartTestName) {
this.dartTestName = dartTestName;
}
private final String dartTestName;
@Test
public void runDartTest() {
PatrolJUnitRunner instrumentation =
(PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation();
instrumentation.runDartTest(dartTestName);
}
}

171
android/gradlew vendored Executable file
View file

@ -0,0 +1,171 @@
#!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# Nix's flutter keeps the flutter_tools/gradle composite build on a read-only
# store path, so every gradle invocation needs its project and Kotlin caches
# redirected off it — the same two arguments the flutter tool itself passes.
# Without them, any gradlew run other than through the flutter tool (patrol_cli,
# manual builds) fails with a silent exit 1.
_nix_cache="${XDG_CACHE_HOME:-$HOME/.cache}/flutter/nix-flutter-tools-gradle/gradle-project-cache"
case " $* " in
*" --project-cache-dir"*) ;;
*) set -- "$@" "--project-cache-dir=$_nix_cache" "-Pkotlin.project.persistent.dir=$_nix_cache/kotlin" ;;
esac
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

View file

@ -10,7 +10,9 @@
"nixpkgs#ninja", "nixpkgs#ninja",
"nixpkgs#pkg-config", "nixpkgs#pkg-config",
"nixpkgs#gtk3", "nixpkgs#gtk3",
"nixpkgs#libsysprof-capture" "nixpkgs#libsysprof-capture",
"nixpkgs#rustup",
"nixpkgs#cargo-ndk"
], ],
"shell": { "shell": {
"init_hook": [ "init_hook": [
@ -22,7 +24,12 @@
"test": "flutter test", "test": "flutter test",
"run-linux": "flutter run -d linux", "run-linux": "flutter run -d linux",
"build-linux": "flutter build linux", "build-linux": "flutter build linux",
"link-android-tools": "if [ -n \"$ANDROID_HOME\" ] && [ ! -d \"$ANDROID_HOME/cmdline-tools/latest\" ] && [ -d \"$ANDROID_HOME/cmdline-tools/8.0\" ]; then ln -s \"$ANDROID_HOME/cmdline-tools/8.0\" \"$ANDROID_HOME/cmdline-tools/latest\"; fi" "link-android-tools": "if [ -n \"$ANDROID_HOME\" ] && [ ! -d \"$ANDROID_HOME/cmdline-tools/latest\" ] && [ -d \"$ANDROID_HOME/cmdline-tools/8.0\" ]; then ln -s \"$ANDROID_HOME/cmdline-tools/8.0\" \"$ANDROID_HOME/cmdline-tools/latest\"; fi",
"build-native": "cd native && cargo build --release",
"build-native-android": "rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android && cd native && cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -o ../android/app/src/main/jniLibs build --release",
"test-native": "cd native && cargo test --release && cargo test --release -- --ignored",
"activate-patrol": "dart pub global activate patrol_cli",
"test-integration": "export PATH=\"$HOME/.pub-cache/bin:$PATH\" && patrol test --dart-define=SMOL_TEST_HOST=$(hostname -I | tr ' ' '\\n' | grep -E '^(192\\.168|10\\.|172\\.)' | head -1)"
} }
}, },
"env": { "env": {

View file

@ -57,6 +57,10 @@
"last_modified": "1970-01-01T00:00:00Z", "last_modified": "1970-01-01T00:00:00Z",
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#android-tools" "resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#android-tools"
}, },
"nixpkgs#cargo-ndk": {
"last_modified": "1970-01-01T00:00:00Z",
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#cargo-ndk"
},
"nixpkgs#clang": { "nixpkgs#clang": {
"last_modified": "1970-01-01T00:00:00Z", "last_modified": "1970-01-01T00:00:00Z",
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#clang" "resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#clang"
@ -84,6 +88,10 @@
"nixpkgs#pkg-config": { "nixpkgs#pkg-config": {
"last_modified": "1970-01-01T00:00:00Z", "last_modified": "1970-01-01T00:00:00Z",
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#pkg-config" "resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#pkg-config"
},
"nixpkgs#rustup": {
"last_modified": "1970-01-01T00:00:00Z",
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#rustup"
} }
} }
} }

View file

@ -0,0 +1,97 @@
// Device integration tests over the real UI, driven by Patrol against the
// system bunshin on the test host. The integration flavor installs beside
// the real app and the instrumentation runner clears its data, so every run
// starts at onboarding with a fresh identity — the app's own flows are the
// subject under test, including the network path.
//
// Run: devbox run test-integration
// (the script resolves the host's LAN IP into SMOL_TEST_HOST, because the
// device reaches the server over Wi-Fi, not loopback.)
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:hive_flutter/hive_flutter.dart";
import "package:path_provider/path_provider.dart";
import "package:patrol/patrol.dart";
import "package:smol_mail/data/providers/providers.dart";
import "package:smol_mail/presentation/app_widget.dart";
import "package:smol_mail/smol/config.dart";
import "package:smol_mail/smol/store.dart";
const testHost = String.fromEnvironment("SMOL_TEST_HOST", defaultValue: "10.0.2.2");
// The system bunshin's static key: a documented, operator-supplied value on
// this machine — the trusted channel SPEC.md §4 asks a pin to come from.
const serverKey = "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq";
Future<Widget> bootApp() async {
// main()'s boot, minus runApp: the test pumps the same tree.
final dataDir = await getApplicationSupportDirectory();
Hive.init(dataDir.path);
final store = await SmolStore.open(dbPath: "${dataDir.path}/smol-mail.db");
applyPresetServer(store);
return ProviderScope(
overrides: [storeProvider.overrideWithValue(store)],
child: const AppWidget(),
);
}
void main() {
patrolTest("smoke: onboarding, register, self-send, fetch, read in requests",
($) async {
await $.pumpWidgetAndSettle(await bootApp());
// Onboarding: a fresh identity, backed up, registered under a name no
// server has bound yet — registration must succeed against the real
// server over Wi-Fi, through the Dart-resolved dial path.
await $("Create Identity").tap();
await $.pumpAndSettle();
await $("I have backed it up").waitUntilVisible();
await $("I have backed it up").tap();
await $.pumpAndSettle();
final user = "it${DateTime.now().millisecondsSinceEpoch.toRadixString(36)}";
// Enter text by TextField, not by its labelText: the decoration label is
// a RichText that is not hit-testable, so a text finder times out.
await $.enterText($(TextField).at(0), "$user@$testHost:1961");
await $.enterText($(TextField).at(1), serverKey);
await $("Pin and Register").tap();
// The register round trip is real network: the inbox tabs only exist
// behind HomeGuard, so reaching them proves the account bound.
await $("Inbox").waitUntilVisible(timeout: const Duration(seconds: 30));
await $("Requests").waitUntilVisible();
// Compose the first self-addressed mail.
await $(Icons.edit).tap();
await $.pumpAndSettle();
await $.enterText($(TextField).at(0), "$user@$testHost:1961");
await $.enterText($(TextField).at(1), "smoke subject");
await $.enterText($(TextField).at(2), "smoke body from patrol");
await $("Send").tap();
// Compose pops back to the inbox once the send round trip completes.
await $("Inbox").waitUntilVisible(timeout: const Duration(seconds: 30));
await $.pumpAndSettle();
// Fetch over the air, then read what arrived.
await $(Icons.cloud_download).tap();
await $.pumpAndSettle(duration: const Duration(seconds: 5));
await $("Requests (1)").tap();
await $.pumpAndSettle();
// First contact is unsolicited, so it landed in requests (sec 5.8) —
// and the §5.6 sent copy must read back as plain text, not <unreadable>.
await $("smoke subject").tap();
await $.pumpAndSettle();
await $("smoke body from patrol").waitUntilVisible();
await $.native.pressBack(); // ignore: deprecated_member_use
await $.pumpAndSettle();
await $("Sent").tap();
await $.pumpAndSettle();
await $("smoke subject").tap();
await $.pumpAndSettle();
await $("smoke body from patrol").waitUntilVisible();
});
}

View file

@ -249,6 +249,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.8.0"
dispose_scope:
dependency: transitive
description:
name: dispose_scope
sha256: "48ec38ca2631c53c4f8fa96b294c801e55c335db5e3fb9f82cede150cfe5a2af"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
equatable:
dependency: transitive
description:
name: equatable
sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -258,7 +274,7 @@ packages:
source: hosted source: hosted
version: "1.3.3" version: "1.3.3"
ffi: ffi:
dependency: transitive dependency: "direct main"
description: description:
name: ffi name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
@ -334,6 +350,11 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_driver:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -384,6 +405,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@ -440,6 +466,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.7" version: "0.15.7"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -464,6 +498,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.10.1" version: "4.10.1"
integration_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -672,6 +711,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
patrol:
dependency: "direct dev"
description:
name: patrol
sha256: "662c50d517b2f159fedbc359a7fb8d0d30cbefea6d5c2a08392a0593079d64d1"
url: "https://pub.dev"
source: hosted
version: "4.10.0"
patrol_finders:
dependency: transitive
description:
name: patrol_finders
sha256: "8608cdacaab90a3b00ecd7b09df11f13a62b01dea13c211b9f13fd86854e103a"
url: "https://pub.dev"
source: hosted
version: "3.6.0"
patrol_log:
dependency: transitive
description:
name: patrol_log
sha256: "3d91c93e8d0ed19cb12d4b27aa24eca7294e0a48d9d8445505c9775feb5fde2d"
url: "https://pub.dev"
source: hosted
version: "0.10.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -712,6 +775,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.5.2" version: "6.5.2"
process:
dependency: transitive
description:
name: process
sha256: "4242ba3508d37e01808bdf71ad1d5bb93a8d671bf2e7450e6b1b353fb0808891"
url: "https://pub.dev"
source: hosted
version: "5.0.6"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -869,6 +940,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.1"
sync_http:
dependency: transitive
description:
name: sync_http
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -1005,6 +1084,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: "28b82ec894fed45dd71c23ba62d1af973ed97dd59a4f5790a4d38b0b13e5657e"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
webkit_inspection_protocol: webkit_inspection_protocol:
dependency: transitive dependency: transitive
description: description:

View file

@ -12,6 +12,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
crypto: ^3.0.3 crypto: ^3.0.3
ffi: ^2.1.0
hive: ^2.2.3 hive: ^2.2.3
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
auto_route: ">=11.1.0 <11.2.0" auto_route: ">=11.1.0 <11.2.0"
@ -24,12 +25,25 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
integration_test:
sdk: flutter
patrol: ^4.10.0
build_runner: ^2.4.6 build_runner: ^2.4.6
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0
auto_route_generator: ^10.5.0 auto_route_generator: ^10.5.0
flutter_native_splash: ^2.3.4 flutter_native_splash: ^2.3.4
flutter_launcher_icons: ^0.14.4 flutter_launcher_icons: ^0.14.4
# patrol test reads this block; android.package_name is the flavored
# applicationId (the integration flavor adds the .integration suffix), and
# patrol_cli refuses to run without it.
patrol:
app_name: kirakira
flavor: integration
test_directory: integration_test
android:
package_name: com.app.smol_mail.integration
flutter_launcher_icons: flutter_launcher_icons:
android: "launcher_icon" android: "launcher_icon"
ios: false ios: false