Skip to content

Commit e66980a

Browse files
authored
feat: add controlMinWidth to AppDimensions and update IntentButton minimum size for improved layout (#497)
- Comprehensive Test Coverage: Implemented extensive unit tests for multiple token types (OTP, Push, Steam, TOTP), Riverpod providers (TokenNotifier), and UI components like container buttons and deletion dialogs. - UI Refactoring & Optimization: Streamlined dialog widgets, improved code readability, and enhanced UI responsiveness by introducing a StatusBar widget and optimizing button states. - Functional Enhancements & Stability: Added new force-delete dialogs, improved error handling via utility functions, and refined logging and validation logic for better application stability.
1 parent 4ff7ac8 commit e66980a

309 files changed

Lines changed: 12537 additions & 7687 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

analysis_options.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
analyzer:
22
errors:
33
constant_identifier_names: ignore
4+
45
include: package:flutter_lints/flutter.yaml
56

67
linter:
78
rules:
9+
use_build_context_synchronously: true
810
unnecessary_string_escapes: false
9-
unnecessary_string_interpolations: false
11+
unnecessary_string_interpolations: false
12+
avoid_redundant_argument_values: true
13+
avoid_void_async: true

integration_test/add_tokens_test.dart

Lines changed: 91 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,15 @@ void main() {
4848
latestStartedVersion: Version.parse('999.999.999'),
4949
),
5050
);
51-
when(mockSettingsRepository.saveSettings(any)).thenAnswer((_) async => true);
51+
when(
52+
mockSettingsRepository.saveSettings(any),
53+
).thenAnswer((_) async => true);
5254
mockTokenRepository = MockTokenRepository();
5355
var tokens = <Token>[];
5456
when(mockTokenRepository.loadTokens()).thenAnswer((_) async => tokens);
55-
when(mockTokenRepository.saveOrReplaceToken(any)).thenAnswer((invocation) async {
57+
when(mockTokenRepository.saveOrReplaceToken(any)).thenAnswer((
58+
invocation,
59+
) async {
5660
final arguments = invocation.positionalArguments;
5761
tokens.removeWhere((element) => element.id == (arguments[0] as Token).id);
5862
tokens.add(arguments[0] as Token);
@@ -64,47 +68,67 @@ void main() {
6468
return true;
6569
});
6670
mockTokenFolderRepository = MockTokenFolderRepository();
67-
when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => const TokenFolderState(folders: []));
68-
when(mockTokenFolderRepository.saveState(any)).thenAnswer((_) async => true);
71+
when(
72+
mockTokenFolderRepository.loadState(),
73+
).thenAnswer((_) async => const TokenFolderState(folders: []));
74+
when(
75+
mockTokenFolderRepository.saveState(any),
76+
).thenAnswer((_) async => true);
6977
mockIntroductionRepository = MockIntroductionRepository();
70-
when(mockIntroductionRepository.loadCompletedIntroductions())
71-
.thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values}));
78+
when(mockIntroductionRepository.loadCompletedIntroductions()).thenAnswer(
79+
(_) async => const IntroductionState(
80+
completedIntroductions: {...Introduction.values},
81+
),
82+
);
7283
});
73-
testWidgets(
74-
'Add Tokens Test',
75-
(tester) async {
76-
await tester.pumpWidget(TestsAppWrapper(
84+
testWidgets('Add Tokens Test', (tester) async {
85+
await tester.pumpWidget(
86+
TestsAppWrapper(
7787
overrides: [
78-
settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)),
79-
tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockTokenRepository)),
80-
tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)),
81-
introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)),
88+
settingsProvider.overrideWith(
89+
() => SettingsNotifier(repoOverride: mockSettingsRepository),
90+
),
91+
tokenProvider.overrideWith(
92+
() => TokenNotifier(repoOverride: mockTokenRepository),
93+
),
94+
tokenFolderProvider.overrideWith(
95+
() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository),
96+
),
97+
introductionNotifierProvider.overrideWith(
98+
() =>
99+
IntroductionNotifier(repoOverride: mockIntroductionRepository),
100+
),
82101
],
83-
child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization),
84-
));
85-
await expectMainViewIsEmptyAndCorrect(tester);
86-
await _addHotpToken(tester);
87-
expect(find.byType(HOTPTokenWidget), findsOneWidget);
88-
await _addTotpToken(tester);
89-
expect(find.byType(TOTPTokenWidget), findsOneWidget);
90-
await _addDaypasswordToken(tester);
91-
expect(find.byType(DayPasswordTokenWidget), findsOneWidget);
92-
await _createFolder(tester);
93-
await tester.pump(const Duration(milliseconds: 200));
94-
expect(find.byType(TokenFolderWidget), findsOneWidget);
95-
expect(find.text(AppLocalizationsEn().folderName), findsOneWidget);
96-
expect(find.byType(TokenWidgetBase).hitTestable(), findsNWidgets(3));
97-
await _moveFolderToTopPosition(tester);
98-
await _moveHotpTokenWidgetIntoFolder(tester);
99-
await _moveDayPasswordTokenWidgetIntoFolder(tester);
100-
expect(find.byType(TOTPTokenWidget).hitTestable(), findsOneWidget);
101-
expect(find.byType(TokenWidgetBase).hitTestable(), findsOneWidget);
102-
await _openFolder(tester);
103-
await pumpUntilFindNWidgets(tester, find.byType(TokenWidgetBase).hitTestable(), 3, const Duration(seconds: 5));
104-
expect(find.byType(TokenWidgetBase).hitTestable(), findsNWidgets(3));
105-
},
106-
timeout: const Timeout(Duration(minutes: 20)),
107-
);
102+
child: PrivacyIDEAAuthenticator(
103+
ApplicationCustomization.defaultCustomization,
104+
),
105+
),
106+
);
107+
await expectMainViewIsEmptyAndCorrect(tester);
108+
await _addHotpToken(tester);
109+
expect(find.byType(HOTPTokenWidget), findsOneWidget);
110+
await _addTotpToken(tester);
111+
expect(find.byType(TOTPTokenWidget), findsOneWidget);
112+
await _addDaypasswordToken(tester);
113+
expect(find.byType(DayPasswordTokenWidget), findsOneWidget);
114+
await _createFolder(tester);
115+
await tester.pump(const Duration(milliseconds: 200));
116+
expect(find.byType(TokenFolderWidget), findsOneWidget);
117+
expect(find.text(AppLocalizationsEn().folderName), findsOneWidget);
118+
expect(find.byType(TokenWidgetBase).hitTestable(), findsNWidgets(3));
119+
await _moveFolderToTopPosition(tester);
120+
await _moveHotpTokenWidgetIntoFolder(tester);
121+
await _moveDayPasswordTokenWidgetIntoFolder(tester);
122+
expect(find.byType(TOTPTokenWidget).hitTestable(), findsOneWidget);
123+
expect(find.byType(TokenWidgetBase).hitTestable(), findsOneWidget);
124+
await _openFolder(tester);
125+
await pumpUntilFindNWidgets(
126+
tester,
127+
find.byType(TokenWidgetBase).hitTestable(),
128+
3,
129+
);
130+
expect(find.byType(TokenWidgetBase).hitTestable(), findsNWidgets(3));
131+
}, timeout: const Timeout(Duration(minutes: 20)));
108132
}
109133

110134
Future<void> _addHotpToken(WidgetTester tester) async {
@@ -174,7 +198,10 @@ Future<void> _createFolder(WidgetTester tester) async {
174198
await tester.pump();
175199
await tester.tap(find.byIcon(Icons.create_new_folder));
176200
await tester.pump(const Duration(milliseconds: 1000));
177-
await tester.enterText(find.byType(TextField).first, AppLocalizationsEn().folderName);
201+
await tester.enterText(
202+
find.byType(TextField).first,
203+
AppLocalizationsEn().folderName,
204+
);
178205
await tester.pump();
179206
await tester.tap(find.text(AppLocalizationsEn().create));
180207
await tester.pump();
@@ -185,7 +212,9 @@ Future<void> _moveFolderToTopPosition(WidgetTester tester) async {
185212
final tokenFolderPosition = tester.getCenter(find.byType(TokenFolderWidget));
186213
final gestrue = await tester.startGesture(tokenFolderPosition);
187214
await tester.pump(const Duration(milliseconds: 1000));
188-
final dragTargetDividerPosition = tester.getCenter(find.byType(DragTargetDivider).first);
215+
final dragTargetDividerPosition = tester.getCenter(
216+
find.byType(DragTargetDivider).first,
217+
);
189218
await gestrue.moveTo(dragTargetDividerPosition);
190219
await tester.pump();
191220
await gestrue.up();
@@ -194,7 +223,9 @@ Future<void> _moveFolderToTopPosition(WidgetTester tester) async {
194223

195224
Future<void> _moveHotpTokenWidgetIntoFolder(WidgetTester tester) async {
196225
await tester.pump();
197-
final tokenWidgetPosition = tester.getCenter(find.byType(HOTPTokenWidget).first);
226+
final tokenWidgetPosition = tester.getCenter(
227+
find.byType(HOTPTokenWidget).first,
228+
);
198229
final gestrue = await tester.startGesture(tokenWidgetPosition);
199230
await tester.pump(const Duration(milliseconds: 1000));
200231
final tokenFolderPosition = tester.getCenter(find.byType(TokenFolderWidget));
@@ -206,7 +237,9 @@ Future<void> _moveHotpTokenWidgetIntoFolder(WidgetTester tester) async {
206237

207238
Future<void> _moveDayPasswordTokenWidgetIntoFolder(WidgetTester tester) async {
208239
await tester.pump();
209-
final tokenWidgetPosition = tester.getCenter(find.byType(DayPasswordTokenWidget).last);
240+
final tokenWidgetPosition = tester.getCenter(
241+
find.byType(DayPasswordTokenWidget).last,
242+
);
210243
final gestrue = await tester.startGesture(tokenWidgetPosition);
211244
await tester.pump(const Duration(milliseconds: 1000));
212245
final tokenFolderPosition = tester.getCenter(find.byType(TokenFolderWidget));
@@ -217,17 +250,28 @@ Future<void> _moveDayPasswordTokenWidgetIntoFolder(WidgetTester tester) async {
217250
}
218251

219252
Future<void> _openFolder(WidgetTester tester) async {
220-
await pumpUntilFindNWidgets(tester, find.byType(TokenFolderWidget), 1, const Duration(seconds: 5));
253+
await pumpUntilFindNWidgets(tester, find.byType(TokenFolderWidget), 1);
221254
await tester.tap(find.byType(TokenFolderWidget));
222255
await tester.pump();
223256
}
224257

225258
Future<void> expectMainViewIsEmptyAndCorrect(WidgetTester tester) async {
226-
await pumpUntilFindNWidgets(tester, find.byType(FloatingActionButton), 1, const Duration(seconds: 10));
259+
await pumpUntilFindNWidgets(
260+
tester,
261+
find.byType(FloatingActionButton),
262+
1,
263+
timeout: const Duration(seconds: 10),
264+
);
227265
expect(find.byType(FloatingActionButton), findsOneWidget);
228-
expect(find.byType(AppBarItem), findsNWidgets(5)); // 4 at BottomNavigationBar and 1 at AppBar
266+
expect(
267+
find.byType(AppBarItem),
268+
findsNWidgets(5),
269+
); // 4 at BottomNavigationBar and 1 at AppBar
229270
expect(find.byType(TokenWidgetBase), findsNothing);
230271
expect(find.byType(TokenFolderWidget), findsNothing);
231-
expect(find.text(ApplicationCustomization.defaultCustomization.appName), findsOneWidget);
272+
expect(
273+
find.text(ApplicationCustomization.defaultCustomization.appName),
274+
findsOneWidget,
275+
);
232276
expect(find.byType(Image), findsOneWidget);
233277
}

integration_test/copy_to_clipboard_test.dart

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import 'package:privacyidea_authenticator/model/riverpod_states/introduction_sta
1010
import 'package:privacyidea_authenticator/model/riverpod_states/settings_state.dart';
1111
import 'package:privacyidea_authenticator/model/riverpod_states/token_folder_state.dart';
1212
import 'package:privacyidea_authenticator/model/tokens/hotp_token.dart';
13-
import 'package:privacyidea_authenticator/utils/customization/application_customization.dart';
1413
import 'package:privacyidea_authenticator/model/version.dart';
14+
import 'package:privacyidea_authenticator/utils/customization/application_customization.dart';
1515
import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/introduction_provider.dart';
1616
import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/settings_notifier.dart';
1717
import 'package:privacyidea_authenticator/utils/riverpod/riverpod_providers/generated_providers/token_folder_notifier.dart';
@@ -28,34 +28,78 @@ void main() {
2828
late final MockIntroductionRepository mockIntroductionRepository;
2929
setUp(() {
3030
mockSettingsRepository = MockSettingsRepository();
31-
when(mockSettingsRepository.loadSettings()).thenAnswer((_) async =>
32-
SettingsState(isFirstRun: false, useSystemLocale: false, localePreference: const Locale('en'), latestStartedVersion: Version.parse('999.999.999')));
33-
when(mockSettingsRepository.saveSettings(any)).thenAnswer((_) async => true);
31+
when(mockSettingsRepository.loadSettings()).thenAnswer(
32+
(_) async => SettingsState(
33+
isFirstRun: false,
34+
useSystemLocale: false,
35+
localePreference: const Locale('en'),
36+
latestStartedVersion: Version.parse('999.999.999'),
37+
),
38+
);
39+
when(
40+
mockSettingsRepository.saveSettings(any),
41+
).thenAnswer((_) async => true);
3442
mockTokenRepository = MockTokenRepository();
35-
when(mockTokenRepository.loadTokens()).thenAnswer((_) async => [
36-
HOTPToken(label: 'test', issuer: 'test', id: 'id', algorithm: Algorithms.SHA256, digits: 6, secret: 'secret', counter: 0),
37-
]);
38-
when(mockTokenRepository.saveOrReplaceTokens(any)).thenAnswer((_) async => []);
43+
when(mockTokenRepository.loadTokens()).thenAnswer(
44+
(_) async => [
45+
HOTPToken(
46+
label: 'test',
47+
issuer: 'test',
48+
id: 'id',
49+
algorithm: Algorithms.SHA256,
50+
digits: 6,
51+
secret: 'secret',
52+
),
53+
],
54+
);
55+
when(
56+
mockTokenRepository.saveOrReplaceTokens(any),
57+
).thenAnswer((_) async => []);
3958
when(mockTokenRepository.deleteTokens(any)).thenAnswer((_) async => []);
4059
mockTokenFolderRepository = MockTokenFolderRepository();
41-
when(mockTokenFolderRepository.loadState()).thenAnswer((_) async => const TokenFolderState(folders: []));
42-
when(mockTokenFolderRepository.saveState(any)).thenAnswer((_) async => true);
60+
when(
61+
mockTokenFolderRepository.loadState(),
62+
).thenAnswer((_) async => const TokenFolderState(folders: []));
63+
when(
64+
mockTokenFolderRepository.saveState(any),
65+
).thenAnswer((_) async => true);
4366
mockIntroductionRepository = MockIntroductionRepository();
44-
when(mockIntroductionRepository.loadCompletedIntroductions())
45-
.thenAnswer((_) async => const IntroductionState(completedIntroductions: {...Introduction.values}));
67+
when(mockIntroductionRepository.loadCompletedIntroductions()).thenAnswer(
68+
(_) async => const IntroductionState(
69+
completedIntroductions: {...Introduction.values},
70+
),
71+
);
4672
});
4773
testWidgets('Copy to Clipboard Test', (tester) async {
48-
await tester.pumpWidget(TestsAppWrapper(
49-
overrides: [
50-
settingsProvider.overrideWith(() => SettingsNotifier(repoOverride: mockSettingsRepository)),
51-
tokenProvider.overrideWith(() => TokenNotifier(repoOverride: mockTokenRepository)),
52-
tokenFolderProvider.overrideWith(() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository)),
53-
introductionNotifierProvider.overrideWith(() => IntroductionNotifier(repoOverride: mockIntroductionRepository)),
54-
],
55-
child: PrivacyIDEAAuthenticator(ApplicationCustomization.defaultCustomization),
56-
));
74+
await tester.pumpWidget(
75+
TestsAppWrapper(
76+
overrides: [
77+
settingsProvider.overrideWith(
78+
() => SettingsNotifier(repoOverride: mockSettingsRepository),
79+
),
80+
tokenProvider.overrideWith(
81+
() => TokenNotifier(repoOverride: mockTokenRepository),
82+
),
83+
tokenFolderProvider.overrideWith(
84+
() => TokenFolderNotifier(repoOverride: mockTokenFolderRepository),
85+
),
86+
introductionNotifierProvider.overrideWith(
87+
() =>
88+
IntroductionNotifier(repoOverride: mockIntroductionRepository),
89+
),
90+
],
91+
child: PrivacyIDEAAuthenticator(
92+
ApplicationCustomization.defaultCustomization,
93+
),
94+
),
95+
);
5796
await tester.pumpAndSettle();
58-
await pumpUntilFindNWidgets(tester, find.text('356 306'), 1, const Duration(seconds: 10));
97+
await pumpUntilFindNWidgets(
98+
tester,
99+
find.text('356 306'),
100+
1,
101+
timeout: const Duration(seconds: 10),
102+
);
59103
expect(find.text('356 306'), findsOneWidget);
60104
await tester.tap(find.text('356 306'));
61105
await tester.pumpAndSettle();

0 commit comments

Comments
 (0)