chore: add initial golden tests

This commit is contained in:
Christian Kußowski committed 2026-10-11 10:55:10 +02:00
1 parent 8f4d0cca37
commit 51242d4007
39 files changed
+171 -17

No files matched your search

+11
View File
@@ -55,6 +55,17 @@ jobs:
- name: Add Firebase Messaging
run: ./scripts/add-firebase-messaging.sh
- run: flutter analyze
flutter_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: subosito/flutter-action@v2
with:
flutter-version-file: .tool_versions.yaml
cache: true
- uses: dtolnay/rust-toolchain@stable
- run: flutter pub get
- run: flutter test
build_debug_apk:
+2
View File
@@ -203,3 +203,5 @@ ios/Runner/GoogleServices-Info.plist
web/e2ee.worker.dart.js
web/e2ee.worker.dart.js.deps
web/e2ee.worker.dart.js.map
test/rust
+1 -1
View File
@@ -26,7 +26,7 @@ To improve the process please make sure that you read the following guidelines c
12. Do not fire&forget a pull request! Contributing also means taking over responsibility, reacting on comments and reviews and be able to collaborate.
13. Do not overengineer something. Not everything needs to be abstracted, if it isn't used in more than one place. Code must fit in the current architecture.
14. Avoid using `else` and prefer function guards, early returns and `switch`.
15. Do not add functionality in the platform-directories. We try to not differ from the default as these creates huge conflicts on upgrading Flutter versions. We do not have our own platform channels but abstracted them in external packages. We must try to get rid of existing workarounds and not extend them!
15. Do not add functionality in the platform-directories. We try to not differ from the default as these creates huge conflicts on upgrading Flutter versions. We do not have our own platform channels but abstracted them in external packages. We must try to get rid of existing workarounds and not extend them! Exceptions are for edge cases like the Notification Service Extension on iOS.
16. Keep all dependencies and external packages minimal. Make sure that new packages are well maintained and no threat to future dependency hell.
# Code Style
+3 -2
View File
@@ -26,7 +26,8 @@ path = [
"integration_test/synapse/**",
"snap/**",
"windows/**",
"web/**"
"web/**",
"test/metadata/**"
]
precedence = "aggregate"
SPDX-FileCopyrightText = "2019-Present Christian Kußowski"
@@ -56,7 +57,7 @@ path = [
"web/icons/**",
"web/splash/**",
"snap/gui/*.png",
"windows/runner/resources/app_icon.ico"
"windows/runner/resources/app_icon.ico",
]
precedence = "aggregate"
SPDX-FileCopyrightText = "2017-Present Fabiyamada"
+4
View File
@@ -27,6 +27,7 @@ class FluffyChatApp extends StatelessWidget {
final List<Client> clients;
final ({String? pincode, bool useBiometrics}) appLockSettings;
final SharedPreferences store;
final Locale? locale;
const FluffyChatApp({
super.key,
@@ -34,6 +35,7 @@ class FluffyChatApp extends StatelessWidget {
required this.clients,
required this.store,
required this.appLockSettings,
this.locale,
});
/// getInitialLink may rereturn the value multiple times if this view is
@@ -87,6 +89,8 @@ class FluffyChatApp extends StatelessWidget {
builder: (context, themeMode, primaryColor) => MaterialApp.router(
title: AppSettings.applicationName.value,
themeMode: themeMode,
debugShowCheckedModeBanner: false,
locale: locale,
theme: FluffyThemes.buildTheme(context, Brightness.light, primaryColor),
darkTheme: FluffyThemes.buildTheme(
context,
+2
View File
@@ -289,6 +289,8 @@ class MatrixState extends State<Matrix> {
),
windows: ClientManager.windowsNotificationSettings,
macOS: const DarwinInitializationSettings(),
iOS: const DarwinInitializationSettings(),
android: const AndroidInitializationSettings('notifications_icon'),
),
onDidReceiveNotificationResponse: (response) => notificationTap(
response,
+9 -1
View File
@@ -879,6 +879,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "18.0.2"
golden_screenshot:
dependency: "direct dev"
description:
name: golden_screenshot
sha256: f1e894eb4d4f8c87a65b7d43ee95d84733f9e1e7980e3e0be5c3165b8b030792
url: "https://pub.dev"
source: hosted
version: "12.1.1"
gsettings:
dependency: transitive
description:
@@ -2338,7 +2346,7 @@ packages:
source: hosted
version: "15.2.0"
vodozemac:
dependency: transitive
dependency: "direct dev"
description:
name: vodozemac
sha256: efce261575f7de97ebd052b16775cd57f940e8212af47d288050c166b9f9a167
+2
View File
@@ -102,9 +102,11 @@ dev_dependencies:
flutter_lints: ^6.0.0
flutter_test:
sdk: flutter
golden_screenshot: ^12.1.1
integration_test:
sdk: flutter
license_checker: ^1.6.2
vodozemac: ^0.8.0
flutter:
generate: true
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2019-Present Christian Kußowski
# SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
#
# SPDX-License-Identifier: AGPL-3.0-or-later
set -e
version=$(yq ".packages.vodozemac.version" < pubspec.lock)
rm -rf ./test/rust dart-vodozemac
git clone https://github.com/famedly/dart-vodozemac.git -b "$version"
mv ./dart-vodozemac/rust ./test/
rm -rf dart-vodozemac
cd ./test/rust && cargo build && cd ../..
+1
View File
@@ -23,6 +23,7 @@ find . -type f \
-not -path "*/snap/*" \
-not -path "*/web/*" \
-not -path "*/windows/*" \
-not -path "*/test/metadata/*" \
-exec reuse annotate \
--copyright="Christian Kußowski" \
--copyright="Contributors to FluffyChat" \
+10 -3
View File
@@ -7,8 +7,8 @@ import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'utils/test_client.dart';
import 'package:matrix/matrix.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
void main() async {
test('Check for missing /command hints', () async {
@@ -16,7 +16,14 @@ void main() async {
jsonDecode(File('lib/l10n/intl_en.arb').readAsStringSync()).keys
.where((String k) => k.startsWith('commandHint_'))
.map((k) => k.replaceFirst('commandHint_', ''));
final commands = (await prepareTestClient()).commands.keys;
final commands = (Client(
'commands',
database: await MatrixSdkDatabase.init(
'test',
database: await databaseFactoryFfi.openDatabase(':memory:'),
sqfliteFactory: databaseFactoryFfi,
),
)).commands.keys;
final missing = commands.where((c) => !translated.contains(c)).toList();
expect(
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

+62
View File
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/widgets/fluffy_chat_app.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_screenshot/golden_screenshot.dart';
import 'package:material_ui/material_ui.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/golden_locales.dart';
import '../utils/test_client.dart';
void main() {
group('ChatList Golden', () {
TestWidgetsFlutterBinding.ensureInitialized();
late Client client;
setUpAll(() async {
client = await prepareTestClient();
client.backgroundSync = false;
});
testGoldens('ChatList Screenshot', (tester) async {
for (final locale in goldenLocals) {
for (final device in GoldenScreenshotDevices.values) {
for (final themeMode in {ThemeMode.dark, ThemeMode.light}) {
client.userDeviceKeys.clear(); // Hide unverified warning
final prefs = await SharedPreferences.getInstance();
await prefs.setString('theme_mode', themeMode.name);
await tester.pumpWidget(
ScreenshotApp.withConditionalTitlebar(
title: AppSettings.applicationName.value,
device: device.device,
home: FluffyChatApp(
clients: [client],
store: prefs,
appLockSettings: (pincode: null, useBiometrics: false),
locale: locale,
),
),
);
await tester.pump(const Duration(milliseconds: 100));
await tester.loadAssets();
await tester.pump(const Duration(milliseconds: 100));
await tester.expectScreenshot(
device.device,
'${themeMode.name}_chat_list_page',
finder: find.byType(MaterialApp).first,
langCode: locale.languageCode,
);
await tester.pumpWidget(const SizedBox());
await tester.pump(const Duration(milliseconds: 100));
}
}
}
});
});
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:flutter/widgets.dart';
const goldenLocals = [Locale('en'), Locale('de')];
+17
View File
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'dart:io';
// ignore: depend_on_referenced_packages
import 'package:vodozemac/vodozemac.dart' as vod;
Future<void> setUpVodozemac() async {
if (vod.isInitialized()) return;
if (!await Directory('./test/rust').exists()) {
await Process.run('./scripts/prepare_vodozemac.sh', []);
}
await vod.init(wasmPath: './pkg/', libraryPath: 'test/rust/target/debug/');
}
+25 -10
View File
@@ -5,20 +5,35 @@
// ignore_for_file: depend_on_referenced_packages
import 'package:fluffychat/config/setting_keys.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:matrix/encryption/utils/key_verification.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'set_up_vodozemac.dart';
// key @test:fakeServer.notExisting
const pickledOlmAccount =
'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg';
class _FakeNotifications extends FlutterLocalNotificationsPlatform {}
Future<Client> prepareTestClient({
bool loggedIn = false,
Uri? homeserver,
String id = 'FluffyChat Widget Test',
}) async {
await setUpVodozemac();
SharedPreferences.setMockInitialValues({});
FlutterLocalNotificationsPlatform.instance = _FakeNotifications();
await AppSettings.init(loadWebConfigFile: false);
homeserver ??= Uri.parse('https://fakeserver.notexisting');
final client = Client(
'FluffyChat Widget Tests',
httpClient: FakeMatrixApi()
..api['GET']!['/.well-known/matrix/client'] = (req) => {},
httpClient: FakeMatrixApi(),
verificationMethods: {
KeyVerificationMethod.numbers,
KeyVerificationMethod.emoji,
@@ -36,13 +51,13 @@ Future<Client> prepareTestClient({
AuthenticationTypes.sso,
},
);
await client.checkHomeserver(homeserver);
if (loggedIn) {
await client.login(
LoginType.mLoginToken,
identifier: AuthenticationUserIdentifier(user: '@alice:example.invalid'),
password: '1234',
);
}
await client.init(
newHomeserver: Uri.parse('https://fakeServer.notExisting'),
newDeviceID: 'ABCD1234',
newDeviceName: 'Screenshot Tester',
newToken: '1234',
newUserID: '@test:fakeServer.notExisting',
newOlmAccount: pickledOlmAccount,
);
return client;
}