Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions lib/screens/settings/add_telegram_setting.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:happy_notes/entities/telegram_settings.dart';
import '../../dependency_injection.dart';
import '../../services/dialog_services.dart';
import 'telegram_sync_settings_controller.dart';
import '../../utils/util.dart'; // Import Util

Expand Down Expand Up @@ -82,16 +83,32 @@ class AddTelegramSettingState extends State<AddTelegramSetting> {
setState(() => _isLoading = true);

try {
await _settingsController.addTelegramSetting(
TelegramSettings(
syncType: _syncType,
syncValue: _syncType == 4 ? _tagController.text : '',
channelId: _channelIdController.text.trim(),
channelName: _channelNameController.text.trim(),
tokenRemark: _remarkController.text.trim(),
encryptedToken: _tokenController.text.trim(),
),
final setting = TelegramSettings(
syncType: _syncType,
syncValue: _syncType == 4 ? _tagController.text : '',
channelId: _channelIdController.text.trim(),
channelName: _channelNameController.text.trim(),
tokenRemark: _remarkController.text.trim(),
encryptedToken: _tokenController.text.trim(),
);
await _settingsController.addTelegramSetting(setting);
if (mounted) {
// The setting is saved but untested and may not sync. Remind the user
// to test it, without blocking — either choice keeps the setting saved.
final testNow = await DialogService.showConfirmDialog(
context,
title: 'Test this setting?',
text: "This setting hasn't been tested yet and may not sync. Test it now?",
noText: 'Later',
yesText: 'Test now',
);
if (testNow == true && mounted) {
final messenger = ScaffoldMessenger.of(context);
if (await _settingsController.testTelegramSetting(context, setting)) {
Util.showInfo(messenger, 'Test message sent successfully.');
}
}
}
if (mounted) {
Navigator.of(context).pop();
}
Expand Down
17 changes: 17 additions & 0 deletions lib/screens/settings/telegram_sync_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ class TelegramSyncSettingsState extends State<TelegramSyncSettings> {
),
),
),
if (!setting.isTested) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
),
child: const Text(
'Untested',
style: TextStyle(
color: Colors.orange,
fontWeight: FontWeight.bold,
),
),
),
],
const Spacer(),
Wrap(
spacing: 8,
Expand Down
98 changes: 98 additions & 0 deletions test/widgets/add_telegram_setting_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:happy_notes/apis/telegram_settings_api.dart';
import 'package:happy_notes/entities/telegram_settings.dart';
import 'package:happy_notes/screens/settings/add_telegram_setting.dart';
import 'package:happy_notes/screens/settings/telegram_sync_settings_controller.dart';
import 'package:happy_notes/services/telegram_settings_service.dart';

import '../test_helpers/seq_logger_setup.dart';

/// Stub controller: records calls and never touches the network.
class StubTelegramSyncSettingsController extends TelegramSyncSettingsController {
StubTelegramSyncSettingsController()
: super(
telegramSettingService:
TelegramSettingsService(telegramSettingsApi: TelegramSettingsApi()),
);

bool addCalled = false;
bool testCalled = false;
bool testResult = true;

@override
Future<bool> addTelegramSetting(TelegramSettings setting) async {
addCalled = true;
return true;
}

@override
Future<bool> testTelegramSetting(BuildContext context, TelegramSettings setting) async {
testCalled = true;
return testResult;
}
}

void main() {
late StubTelegramSyncSettingsController stub;

setUp(() async {
setupSeqLoggerForTesting();
await GetIt.instance.reset();
stub = StubTelegramSyncSettingsController();
GetIt.instance.registerSingleton<TelegramSyncSettingsController>(stub);
});

tearDown(() async {
await GetIt.instance.reset();
});

// Note: while the reminder dialog is open, _saveSetting is still awaiting it,
// so the save-button spinner (CircularProgressIndicator) is still animating.
// pumpAndSettle would time out on that perpetual animation — pump fixed
// durations instead.
Future<void> settle(WidgetTester tester) async {
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
}

Future<void> fillAndSave(WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: AddTelegramSetting()));
await tester.enterText(find.widgetWithText(TextFormField, 'Channel ID'), '-100123');
await tester.enterText(find.widgetWithText(TextFormField, 'Channel Name'), 'My Channel');
await tester.enterText(find.widgetWithText(TextFormField, 'Telegram Bot Token'), 'token');
await tester.enterText(find.widgetWithText(TextFormField, 'Token Remark'), 'remark');
await tester.tap(find.text('Save Settings'));
await settle(tester);
}

testWidgets('save shows a non-blocking test reminder', (tester) async {
await fillAndSave(tester);

expect(stub.addCalled, isTrue);
expect(find.text('Test now'), findsOneWidget);
expect(find.text('Later'), findsOneWidget);
});

testWidgets('choosing Later dismisses without testing', (tester) async {
await fillAndSave(tester);

await tester.tap(find.text('Later'));
await settle(tester);

expect(stub.testCalled, isFalse);
// Dialog dismissed; the setting stays saved (save was never blocked).
expect(find.text('Test now'), findsNothing);
});

testWidgets('choosing Test now runs the test flow', (tester) async {
await fillAndSave(tester);

await tester.tap(find.text('Test now'));
await settle(tester);

expect(stub.testCalled, isTrue);
expect(find.text('Test now'), findsNothing);
});
}
Loading