smfAppTests function
The tests that the matrix of the modules of smf create adds to its
apps, which the CLI and the packages of its modules keep in their
app_tests, and the roles whose contract they check with every
provider; tool/matrix.dart runs them in CI.
Only that tool, the tests of the CLI and the app of several providers of the fixture registry, which runs them next to other providers of their roles, use them: they name the modules whose app tests they register and the roles whose contract those check, which the binary knows nothing of.
Implementation
Future<MatrixAppTests> smfAppTests() async {
final cli = await appTestsDirectoryOf('smf_flutter_cli');
final firebaseCore = await appTestsDirectoryOf('smf_firebase_core');
final crashlytics = await appTestsDirectoryOf('smf_firebase_crashlytics');
final analytics = await appTestsDirectoryOf('smf_firebase_analytics');
final firebaseAuth = await appTestsDirectoryOf('smf_firebase_auth');
final googleSignIn = await appTestsDirectoryOf('smf_google_sign_in');
final appleSignIn = await appTestsDirectoryOf('smf_apple_sign_in');
final messaging = await appTestsDirectoryOf('smf_firebase_messaging');
final home = await appTestsDirectoryOf('smf_home_flutter');
final localNotifications = await appTestsDirectoryOf(
'smf_local_notifications',
);
final onboarding = await appTestsDirectoryOf('smf_onboarding');
final settings = await appTestsDirectoryOf('smf_settings');
final sharedPreferences = await appTestsDirectoryOf(
'smf_shared_preferences',
);
final signIn = await appTestsDirectoryOf('smf_sign_in');
return MatrixAppTests(
[
// The app starts and shows its first screen: a check that CI builds
// as the entry of the app and starts on an Android emulator and on an
// iOS simulator with .github/scripts/start_app.sh, in the apps that
// it adds it to with --add-app-tests. It knows no module, only main()
// of lib/main.dart, which the app entry role puts into every app
// whichever module provides it, so the CLI keeps it and it applies to
// every app. In the apps of the matrix it is only analyzed, and
// flutter test runs the tests that the modules put into each app.
// Once the first screen settled, it runs the probes of the tests that
// go into the app with it, which go through the roles of the app,
// such as the walk of its routes.
MatrixAppTest(
'$cli/start',
appliesTo: (_) => true,
readsStartProbes: true,
),
// The start-up of the app initializes Firebase, with the options that
// `flutterfire configure` would write, and the mocks of Firebase Core,
// which the matrix sets up for the tests of every module of the app.
MatrixAppTest(
'$firebaseCore/firebase_core',
appliesTo: _has(FirebaseCoreModule.id),
devDependencies: const ['firebase_core_platform_interface'],
mocks: const MatrixMocks(
'test/firebase_core_mocks.dart',
'mockFirebaseCore',
),
),
// The crash reporter of the module reaches Crashlytics, and the
// errors that nothing catches reach it through the handlers that the
// start-up of the app installs. The tests look only at what reaches
// Crashlytics, not at the other crash reporters that the app may
// have.
MatrixAppTest(
'$crashlytics/firebase_crashlytics',
appliesTo: _has(FirebaseCrashlyticsModule.id),
devDependencies: const ['firebase_crashlytics_platform_interface'],
mocks: const MatrixMocks(
'test/firebase_crashlytics_mocks.dart',
'mockFirebaseCrashlytics',
),
),
// The analytics service of the module reaches Firebase Analytics. The
// test leaves out the other analytics services that the app may have.
MatrixAppTest(
'$analytics/firebase_analytics',
appliesTo: _has(FirebaseAnalyticsModule.id),
mocks: const MatrixMocks(
'test/firebase_analytics_mocks.dart',
'mockFirebaseAnalytics',
),
),
// The first screen of an app with a router is logged once, under the
// name of the screen that the app starts on (see _startScreenOf),
// whichever module provides the router, which calls the listener of
// the screen of Firebase Analytics: a test of the router role too, so
// it finds the apps with a router by their roles.
MatrixAppTest(
'$analytics/screen_views',
appliesTo: (app) =>
_has(FirebaseAnalyticsModule.id)(app) &&
app.hook!.presentRoles.contains(routerRole),
values: (app) => {'start_screen': _startScreenOf(app)},
roles: {routerRole},
),
// The sign-in service of the module on Firebase Authentication: what
// each of its calls sends to Firebase, with a password and with the
// credentials of Google and Apple; the credential that Firebase gave
// with a refused link, which the sign-in after it takes; the
// revocation of the tokens of Apple before a deletion; the reason
// that it gives each code of Firebase, in the form in which Android
// and iOS send it; the user whom it finds on the device when it is
// created; a call for a user whose session has ended on the server,
// which signs that user out; and the language in which it asks
// Firebase for the message of a password reset, the one that the app
// is in (see _messageLanguagesOf). The tests use the service of the
// module, and leave what every provider of the auth role does to the
// test of the role. The mocks are a backend in memory with no account
// and nobody signed in, which takes the test tokens of Google and
// Apple, and the matrix sets them up for the tests of every module of
// the app, since the start-up of the app starts the session of the
// app on Firebase Authentication. Its probe compares, on a device, the
// user of the session with the one that the Firebase SDK has there.
MatrixAppTest(
'$firebaseAuth/firebase_auth',
appliesTo: _has(FirebaseAuthModule.id),
devDependencies: const ['firebase_auth_platform_interface'],
generatedFiles: _messageLanguagesOf,
mocks: const MatrixMocks(
'test/firebase_auth_mocks.dart',
'mockFirebaseAuth',
),
startProbe: const MatrixStartProbe(
'integration_test/firebase_auth/probe.dart',
'probeFirebaseAuth',
),
),
// The way to sign in with Google of the module, on the real
// google_sign_in package: the package is started once, without the
// ids of the clients of Google, and the credential is the ID token of
// the Google account; a closed sheet returns null, the errors with
// which Google refuses an app that is not set up for it fail with
// notConfigured, and any other error with unknown; and the app lists
// it on Android and iOS, with the logo of Google from its assets. The
// mocks replace the platform implementation of the package with
// Google for one account, and the matrix sets them up for the tests
// of every module of the app, such as the test of the ways to sign in
// of the auth role. The test has no probe for the start check: nobody
// can sign in on a device.
MatrixAppTest(
'$googleSignIn/google_sign_in',
appliesTo: _has(GoogleSignInModule.id),
devDependencies: const ['google_sign_in_platform_interface'],
mocks: const MatrixMocks(
'test/google_sign_in_mocks.dart',
'mockGoogleSignIn',
),
),
// The way to sign in with Apple of the module, on the real
// sign_in_with_apple package: on iOS, the request has the SHA-256
// hash of a new raw nonce, and the credential is the ID token of
// Apple with that raw nonce, the authorization code of the request
// and the name, which only the first sign-in has; a closed sheet
// returns null, the unknown error of an app without the capability
// fails with notConfigured, and any other error with unknown; on
// another platform it fails with notConfigured without asking Apple;
// and the app lists it on iOS only, with an icon in the colour of the
// label. The mocks answer the method channel of the package as Apple
// does on iOS, for one user and with the nonce of the request in the
// ID token, and the matrix sets them up for the tests of every module
// of the app, such as the test of the ways to sign in of the auth
// role. The test has no probe for the start check: nobody can sign in
// on a device.
MatrixAppTest(
'$appleSignIn/apple_sign_in',
appliesTo: _has(AppleSignInModule.id),
mocks: const MatrixMocks(
'test/apple_sign_in_mocks.dart',
'mockSignInWithApple',
),
),
// The push service of the module on Firebase Cloud Messaging: what
// each of its calls sends to Firebase, on Android and on iOS, such as
// the presentation options of iOS, which let a notification show in
// the foreground, and never a request for the permission to show
// notifications; the address, which is the registration token, and
// none where Firebase has none or fails; and a message in the
// foreground, a tap, a new token and the message whose tap launched
// the app, with the data of a message as texts, through the service
// and through appPush. The tests use the service of the module, and
// leave what every provider of the push role does to the test of the
// role. The mocks are a device in memory with a registration token
// that stays the same, and the matrix sets them up for the tests of
// every module of the app, since the start-up of the app starts the
// push messages. Its probe compares, on a device, the address of the
// app with the token that the Firebase SDK has there.
MatrixAppTest(
'$messaging/firebase_messaging',
appliesTo: _has(FirebaseMessagingModule.id),
mocks: const MatrixMocks(
'test/firebase_messaging_mocks.dart',
'mockFirebaseMessaging',
),
startProbe: const MatrixStartProbe(
'integration_test/firebase_messaging/probe.dart',
'probeFirebaseMessaging',
),
),
// The onboarding of the module: on its first launch, the app shows
// it in place of the screen that it starts on (see
// _startScreenFileOf), and the button of its last page or Skip saves
// that it is finished and shows that screen; an app that finds it
// finished goes straight there. The onboarding has its texts in each
// language of the app, as the device asks for it (see
// _onboardingTextsFileOf). The router
// leaves the onboarding, whichever module provides it, as the router
// role says of the guards of the routes: a test of the router role
// too. The mocks finish the onboarding before the app starts, in
// memory, and the matrix sets them up for the tests of every module
// of the app, which expect the screens that the guard of the
// onboarding would keep them from. Its probe goes through the
// onboarding on a device, where a first launch finds nothing saved,
// unless the onboarding is finished there. The test comes before the
// walk of the routes in this list, whose probe then goes through the
// routes of an app past its onboarding. For the route of the
// onboarding, whose flow is over by then, the walk expects the screen
// that the app starts on, or the target of a guard that does not
// allow there. So on a device the start check leaves the app with the
// onboarding finished.
MatrixAppTest(
'$onboarding/onboarding',
appliesTo: _has(OnboardingModule.id),
generatedFiles: (app, packageName) => {
..._startScreenFileOf(app, packageName),
..._onboardingTextsFileOf(app),
},
roles: {routerRole},
mocks: const MatrixMocks(
'test/onboarding_mocks.dart',
'finishOnboarding',
),
startProbe: const MatrixStartProbe(
'integration_test/onboarding/probe.dart',
'probeOnboarding',
),
),
// The settings screen of the module. Its title is in the language of
// the app: the test goes through the languages of the app that the
// module has its title in, which the matrix writes from the
// localization role of the app, English alone for an app without the
// role. Below the title it shows the entries of the app in one
// group, or, in an app without settings, a note for its developer
// with the path of the file of the screen, which a tap copies. And
// it has a back button only on top of another screen, where the
// button leads back. For these, the matrix writes how many entries
// the screen has and whether it is a destination of the main
// navigation, where code cannot push it (see _settingsOfAppOf).
MatrixAppTest(
'$settings/settings',
appliesTo: _has(SettingsModule.id),
generatedFiles: (app, packageName) => {
..._settingsLanguagesOf(app, packageName),
..._settingsOfAppOf(app),
},
),
// The preferences of the module reach shared_preferences, and read
// what it has when they are opened, lists in the form that each
// platform returns them in. The mocks keep the platform side of the
// package in memory, and the matrix sets them up for the tests of
// every module of the app, since the start-up of the app opens the
// preferences. Its probe opens the preferences again on a device,
// where the platform side is the real one, and reads back what it
// saved.
MatrixAppTest(
'$sharedPreferences/shared_preferences',
appliesTo: _has(SharedPreferencesModule.id),
devDependencies: const ['shared_preferences_platform_interface'],
mocks: const MatrixMocks(
'test/shared_preferences_mocks.dart',
'mockSharedPreferences',
),
startProbe: const MatrixStartProbe(
'integration_test/shared_preferences/probe.dart',
'probeSharedPreferences',
),
),
// The notifications service of the module on
// flutter_local_notifications: what each of its calls sends to the
// plugin, on Android and on iOS, such as the channel of the app with a
// high importance, the cancel of the scheduled notification of an id
// that is shown, and an inexact alarm at a time in UTC; a request that
// shows the question of the system, or opens the settings of the
// system where the system shows none; and the taps on a notification,
// the one that launched the app included, with the data of the
// notification. The tests use the service of the module, and leave
// what every provider of the notifications role does to the test of
// the role. The mocks are a device in memory on which the app may not
// show notifications and has not asked, and the matrix sets them up
// for the tests of every module of the app, since the start-up of the
// app opens the notifications. Its probe checks, on an Android
// device, the channel of the app that the service created.
MatrixAppTest(
'$localNotifications/local_notifications',
appliesTo: _has(LocalNotificationsModule.id),
mocks: const MatrixMocks(
'test/local_notifications_mocks.dart',
'mockLocalNotifications',
),
startProbe: const MatrixStartProbe(
'integration_test/local_notifications/probe.dart',
'probeLocalNotifications',
),
),
// The start screen of the module, a welcome to the developer of the
// app, wherever the app starts: it names the app, greets by the time
// of the day and lists the next steps with their paths, with its
// texts in each language of the app (see _homeTextsFileOf); a tap on
// a step copies its path and says so; its parts come in once, at
// once in an app that asks for less motion; and it fits a small
// phone with a large text size. The tests go to the route of the
// module through the navigation of the router role, so they hold in
// an app that starts on another screen too.
MatrixAppTest(
'$home/home',
appliesTo: _has(HomeModule.id),
generatedFiles: (app, packageName) => _homeTextsFileOf(app),
),
// The screens of sign-in of the module, in each mode of the auth
// role, which the app has as a constant: an app that asks for an
// account shows the sign-in on its first launch in place of the
// screen that it starts on (see _signInOfAppOf), and an app that
// everyone may use shows it once code asks for it; a sign-up, a
// sign-in and a sign-out go through the session of the app, and an
// anonymous user keeps the id with the account; the forms show what
// is wrong with an email address and a password, the text of each
// failure of a call in each language of the app, and that a call is
// on its way; a password reset tells where the message went; and the
// screens fit a small phone with a large text size. The sign-in and
// the sign-up have a button for each way to sign in of the app that
// works on the platform, from appSignInMethods of the auth role, which
// signs in through the session with the mocks of the module of the way
// to sign in, spins while its call is on its way and shows its failure
// below the buttons, on Android and on iOS. The screen of
// the account signs out and deletes the account, which it asks about
// first in a sheet, with texts of its own for a deletion that the
// provider refused, and the entry of the settings screen leads to
// it, through the sign-in for a user without an account. For them
// the matrix writes the settings screen of the app, whether it is a
// destination of the main navigation, and the entry that the module
// gave it, from the settings screen role and the layout role (see
// _signInOfAppOf). The screens only change the session: the router
// shows them and leaves them, whichever module provides it, as the
// router role says of the guards of the routes and of the routes that
// ask for a condition, so this is a test of the router role too.
// The mocks sign a test account up before the app starts, through
// the session, and the matrix sets them up for the tests of every
// module of the app, which expect the screens that the guards of the
// sign-in would keep them from. The test has no probe for the start
// check: nobody can sign in on a device, and the walk of the routes
// shows the screens there.
MatrixAppTest(
'$signIn/sign_in',
appliesTo: _has(SignInModule.id),
generatedFiles: _signInOfAppOf,
roles: {routerRole},
mocks: const MatrixMocks(
'test/sign_in_mocks.dart',
'signUpTestAccount',
),
),
// The services of the apps whose modules register some in the DI
// container, whichever module provides it.
await diRoleAppTest(),
// The events of the apps with the events role, whichever module
// provides it.
await eventsRoleAppTest(),
// The preferences of the apps with the preferences role, whichever
// module provides it.
await preferencesRoleAppTest(),
// The sign-in of the apps with the auth role, whichever module
// provides it, in the mode that the role chose for each: the service
// of the provider keeps its contract, and the session of the app
// does what the mode says.
await authRoleAppTest(),
// The ways to sign in of the apps whose modules give the auth role
// one, whichever modules provide the role and the ways to sign in:
// the service takes the credentials of identities of each issuer of
// the app as the role says, and the session signs in with the
// credential that the function of each way to sign in returns, under
// the mocks of its module, in each mode of the role.
await authMethodsAppTest(),
// The notifications of the apps with the notifications role,
// whichever module provides it: the permission follows what the
// service says and what the app asked, a request asks once, the taps
// of the app and of an owner go apart, a scheduled notification is
// listed until it is cancelled or shown, and the next start keeps
// what was scheduled and the permission.
await notificationsRoleAppTest(),
// The entry of the notifications on the settings screen of the apps
// with the notifications role and the settings screen role, whichever
// modules provide them: it shows whether the app may show
// notifications in the language of the app, a tap asks while the app
// has not asked and opens the settings of the system after that, and
// the entry follows what the user changed there.
await notificationsSettingAppTest(),
// The step of the onboarding that asks for the permission to show
// notifications, in the apps with the notifications role and the
// onboarding screen role, whichever modules provide them: the
// onboarding shows it only while the app has not asked, and its action
// asks.
await notificationsStepAppTest(),
// The push messages of the apps with the push role, whichever module
// provides it: the app follows the address of the service, shows a
// message in the foreground where the platform does not, gives each
// tap once, and the next start has the same address.
await pushRoleAppTest(),
// The routes of the apps with a router, whichever module provides
// it: the test starts the app and goes to each location that needs
// no values.
await routerWalkAppTest(),
// The settings screen of the apps with the settings screen role,
// whichever module provides it: the route that the provider names
// shows the screen, and the screen shows every entry that the
// modules of the app give the role once, one below the other in the
// order of the role.
MatrixAppTest(
'$cli/settings_screen_role',
appliesTo: (app) => app.hook!.presentRoles.contains(settingsScreenRole),
generatedFiles: _settingsOf,
roles: {settingsScreenRole},
),
// The theme mode of the apps with the theme role, whichever module
// provides it: the root of the app takes the mode that is chosen, and
// the screen below it gets the light or the dark theme of the
// provider; a choice is saved under the key of the role; and the next
// start has the mode that is saved. A test of the app entry role too,
// whose provider builds the root.
await themeRoleAppTest(),
// The entry of the theme mode on the settings screen, in the apps
// with the theme role and the settings screen role, whichever modules
// provide them: the screen shows the entry, which shows the mode of
// the app, also one that other code chose, and a tap on a mode
// chooses it.
MatrixAppTest(
'$cli/theme_setting',
appliesTo: (app) => app.hook!.presentRoles
.containsAll(const [themeRole, settingsScreenRole]),
values: _themeValuesOf,
generatedFiles: _themeSettingOf,
roles: {themeRole},
),
// The languages and the texts of the apps with the localization
// role, whichever module provides it: the root supports the languages
// of the app, with a delegate of each kind for each of them, the app
// and its texts follow the language that the user chose, and the
// choice is saved and restored. Its probe goes through the languages
// on a device.
await localizationRoleAppTest(),
// The setting of the language on the settings screen of the apps
// with the localization role and the settings screen role, whichever
// modules provide them: its sheet chooses a language of the app,
// which the app is then in and remembers, or the languages of the
// device.
await languageSettingAppTest(),
],
// Each provider of the router role gets a test of the listeners of the
// screen, the fixture registry tests the rest of the role, and each
// provider of the DI role, of the events role, of the preferences
// role, of the auth role, of the notifications role, of the push role,
// of the settings screen role, of the theme role and of the
// localization role gets the tests of its role. Each provider of the
// app entry role gets the test of the theme role, which checks that its
// root rebuilds.
testedRoles: {
routerRole,
diRole,
eventsRole,
preferencesRole,
authRole,
notificationsRole,
pushRole,
settingsScreenRole,
themeRole,
appEntryRole,
localizationRole,
},
);
}