feat: align shared app foundations
This commit is contained in:
1 parent
bd6bb0ffce
commit
c5653a3699
21 files changed
+894
-66
No files matched your search
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jinzhi_flutter_app/features/settings/application/feedback_email_service.dart';
|
||||
|
||||
void main() {
|
||||
test('encodes feedback email subject and body as a mailto URI', () {
|
||||
final uri = FeedbackEmailService.composeMailtoUri(
|
||||
recipient: 'support@example.com',
|
||||
subject: '金值反馈',
|
||||
body: '问题描述\n第二行',
|
||||
);
|
||||
|
||||
expect(uri.scheme, 'mailto');
|
||||
expect(uri.path, 'support@example.com');
|
||||
expect(uri.queryParameters['subject'], '金值反馈');
|
||||
expect(uri.queryParameters['body'], '问题描述\n第二行');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jinzhi_flutter_app/common/lifecycle/refresh_coordinator.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('refreshes active registrations when returning to foreground', (
|
||||
tester,
|
||||
) async {
|
||||
final coordinator = RefreshCoordinator();
|
||||
addTearDown(coordinator.dispose);
|
||||
var activeCalls = 0;
|
||||
var inactiveCalls = 0;
|
||||
coordinator.register(onRefresh: () => activeCalls++);
|
||||
coordinator.register(onRefresh: () => inactiveCalls++, active: false);
|
||||
|
||||
coordinator.didChangeAppLifecycleState(AppLifecycleState.resumed);
|
||||
await tester.pump();
|
||||
|
||||
expect(activeCalls, 1);
|
||||
expect(inactiveCalls, 0);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jinzhi_flutter_app/common/storage/secure_identity_storage.dart';
|
||||
import 'package:jinzhi_flutter_app/common/storage/storage_keys.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() => SharedPreferences.setMockInitialValues({}));
|
||||
|
||||
test(
|
||||
'migrates a legacy SharedPreferences token into secure storage',
|
||||
() async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
storageLegacyAccessToken: ' legacy ',
|
||||
});
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
final secure = _MemorySecureStorage();
|
||||
final storage = SecureIdentityStorage(preferences, secureStorage: secure);
|
||||
|
||||
expect(await storage.restoreAccessToken(), 'legacy');
|
||||
expect(await secure.read('access_token'), 'legacy');
|
||||
expect(preferences.containsKey(storageLegacyAccessToken), isFalse);
|
||||
expect(preferences.getBool(storageAuthSessionActive), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'migrates the previous secure jwt key when no current token exists',
|
||||
() async {
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
final secure = _MemorySecureStorage({'jwt_token': 'secure-legacy'});
|
||||
final storage = SecureIdentityStorage(preferences, secureStorage: secure);
|
||||
|
||||
expect(await storage.restoreAccessToken(), 'secure-legacy');
|
||||
expect(await secure.read('access_token'), 'secure-legacy');
|
||||
expect(await secure.read('jwt_token'), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class _MemorySecureStorage implements SecureKeyValueStorage {
|
||||
_MemorySecureStorage([Map<String, String>? initial])
|
||||
: _values = {...?initial};
|
||||
|
||||
final Map<String, String> _values;
|
||||
|
||||
@override
|
||||
Future<void> delete(String key) async => _values.remove(key);
|
||||
|
||||
@override
|
||||
Future<String?> read(String key) async => _values[key];
|
||||
|
||||
@override
|
||||
Future<void> write(String key, String value) async {
|
||||
_values[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jinzhi_flutter_app/common/theme/jinzhi_theme.dart';
|
||||
import 'package:jinzhi_flutter_app/common/widgets/app_network_image.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('uses the standard fallback for an empty image URL', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
ShadApp(
|
||||
theme: ShadThemeData(colorScheme: jinzhiLightScheme),
|
||||
home: const Scaffold(body: AppNetworkImage(imageUrl: ' ')),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byIcon(Icons.image_not_supported_outlined), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('uses a supplied fallback for an empty image URL', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
ShadApp(
|
||||
theme: ShadThemeData(colorScheme: jinzhiLightScheme),
|
||||
home: const Scaffold(
|
||||
body: AppNetworkImage(
|
||||
imageUrl: '',
|
||||
errorWidget: Text('image unavailable'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('image unavailable'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user