Files
FlClash/test/views/add_profile_view_test.dart
chen08209 9bd2286eb5 feat(ui): layer surfaces with fills and follow the iOS type scale
Changelog: iOS-style surfaces, alerts and sheets; clearer TV focus
2026-10-17 17:21:43 +08:00

244 lines
7.0 KiB
Dart

import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/features/form/form.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/action.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/providers/database.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/views/profiles/add.dart';
import 'package:material_ui/material_ui.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import '../helpers/test_app.dart';
import '../helpers/test_profiles.dart';
class _RecordingProfilesAction extends ProfilesAction {
final batches = <List<String>>[];
final singles = <String>[];
final ageSecretKeys = <String>[];
@override
Future<void> addProfileFromLink(
String link, {
String? label,
String ageSecretKey = '',
}) async {
singles.add(link);
ageSecretKeys.add(ageSecretKey);
}
@override
Future<void> addProfilesFromLinks(List<String> links) async =>
batches.add(links);
}
Finder _field(String title) => find.descendant(
of: find.widgetWithText(FormRow, title),
matching: find.byType(TextFormField),
);
ProviderContainer _containerFor(
WidgetTester tester, {
ProfilesAction? profilesAction,
List<Profile> profiles = const [],
}) {
const size = Size(1400, 1000);
tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final container = ProviderContainer(
overrides: [
profilesProvider.overrideWith(() => TestProfiles(profiles)),
if (profilesAction != null)
profilesActionProvider.overrideWith(() => profilesAction),
],
);
addTearDown(container.dispose);
globalState.container = container;
container.read(viewSizeProvider.notifier).update((_) => size);
return container;
}
void main() {
testWidgets('lists the QR code, file, link, and custom entries', (
tester,
) async {
final container = _containerFor(tester);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: TestApp(
child: Scaffold(
body: Builder(
builder: (context) =>
AddProfileView(context: context, origin: context),
),
),
),
),
);
await tester.pumpAndSettle();
final l10n = currentAppLocalizations;
expect(find.text(l10n.qrcode), findsOne);
expect(find.text(l10n.file), findsOne);
expect(find.text(l10n.link), findsOne);
expect(find.text(l10n.customProfile), findsOne);
expect(tester.takeException(), null);
});
testWidgets('a batch link import skips existing profiles and adds the rest', (
tester,
) async {
final action = _RecordingProfilesAction();
final container = _containerFor(
tester,
profilesAction: action,
profiles: [Profile.normal(url: 'https://example.com/old')],
);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: TestApp(
child: Scaffold(
body: Builder(
builder: (context) =>
AddProfileView(context: context, origin: context),
),
),
),
),
);
await tester.pumpAndSettle();
final l10n = currentAppLocalizations;
await tester.tap(find.text(l10n.link));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip(l10n.batchImport));
await tester.pumpAndSettle();
await tester.enterText(
find.byType(TextField),
'https://example.com/a\nhttps://example.com/old\nhttps://example.com/b',
);
await tester.pump();
await tester.tap(find.text(l10n.submit));
await tester.pumpAndSettle();
expect(action.batches, [
['https://example.com/a', 'https://example.com/b'],
]);
expect(tester.takeException(), null);
});
testWidgets('the link import takes a proxy share link', (tester) async {
final action = _RecordingProfilesAction();
final container = _containerFor(tester, profilesAction: action);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: TestApp(
child: Scaffold(
body: Builder(
builder: (context) =>
AddProfileView(context: context, origin: context),
),
),
),
),
);
await tester.pumpAndSettle();
final l10n = currentAppLocalizations;
await tester.tap(find.text(l10n.link));
await tester.pumpAndSettle();
await tester.enterText(
_field(l10n.link),
'trojan://pass@example.com:443#HK',
);
await tester.pump();
await tester.tap(find.text(l10n.submit));
await tester.pumpAndSettle();
expect(action.singles, ['trojan://pass@example.com:443#HK']);
expect(tester.takeException(), null);
});
testWidgets('the link import passes the age secret key along', (
tester,
) async {
final action = _RecordingProfilesAction();
final container = _containerFor(tester, profilesAction: action);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: TestApp(
child: Scaffold(
body: Builder(
builder: (context) =>
AddProfileView(context: context, origin: context),
),
),
),
),
);
await tester.pumpAndSettle();
final l10n = currentAppLocalizations;
await tester.tap(find.text(l10n.link));
await tester.pumpAndSettle();
await tester.enterText(_field(l10n.link), 'https://example.com/sub');
await tester.enterText(_field(l10n.ageSecretKey), ' AGE-SECRET-KEY-1A ');
await tester.pump();
await tester.tap(find.text(l10n.submit));
await tester.pumpAndSettle();
expect(action.singles, ['https://example.com/sub']);
expect(action.ageSecretKeys, ['AGE-SECRET-KEY-1A']);
expect(tester.takeException(), null);
});
testWidgets('the link import rejects what is neither kind of link', (
tester,
) async {
final action = _RecordingProfilesAction();
final container = _containerFor(tester, profilesAction: action);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: TestApp(
child: Scaffold(
body: Builder(
builder: (context) =>
AddProfileView(context: context, origin: context),
),
),
),
),
);
await tester.pumpAndSettle();
final l10n = currentAppLocalizations;
await tester.tap(find.text(l10n.link));
await tester.pumpAndSettle();
await tester.enterText(
_field(l10n.link),
'wireguard://key@example.com:51820',
);
await tester.pump();
await tester.tap(find.text(l10n.submit));
await tester.pumpAndSettle();
expect(find.text(l10n.invalidLinkTip), findsOne);
expect(action.singles, isEmpty);
expect(tester.takeException(), null);
});
}