Files
oott/frontend/test/widget/settings_push_test.dart
T
rzuastiandClaude Opus 4.8 475edcb2cf Report delivered device count from the test-notification endpoint
POST /api/notifications/test previously returned a blanket 200 even when there
were no registered devices, so a test that reached nobody looked like a success.
push::send now returns the number of devices the relay confirmed delivery to, and
the endpoint returns it as {"delivered": N}. Settings shows "sent to N device(s)"
on success and an explicit "No devices are registered..." warning when N is 0,
which is the case that previously masqueraded as success.

Backend, API and widget tests updated; clippy and dart analyze clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:06:56 -04:00

214 lines
6.5 KiB
Dart

import 'package:encrypter/encrypter/xor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:frontend/settings/settings.dart';
import 'package:frontend/utils/pref_utils.dart';
import 'package:frontend/utils/push_service.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import '../helpers/backend_test_harness.dart';
import '../helpers/pump_app.dart';
// A fake so the toggle can be exercised without Firebase.
class _FakePushService implements PushService {
_FakePushService({this.supported = true, this.enableResult = true});
final bool supported;
final bool enableResult;
int enableCalls = 0;
int disableCalls = 0;
@override
bool get isSupported => supported;
@override
Future<bool> enable() async {
enableCalls++;
return enableResult;
}
@override
Future<void> disable() async {
disableCalls++;
}
}
const _toggleText = 'Push notifications on this device';
void main() {
late DioAdapter adapter;
setUp(() async {
adapter = await setUpBackendForTest(
prefs: {
'base_url': 'http://my.server/api',
'api_key': XOR().xorEncode('topsecret'),
'theme': 'catppuccin_mocha',
},
);
// The mock SharedPreferences persist across tests in this isolate, so reset
// the per-device push intent to a known-off baseline before each test.
await PrefUtil.setValue('push_enabled', false);
});
// Stubs the backend config endpoint the settings screen reads on load.
void stubNotificationMethod(String method) {
adapter.onGet(
'/config',
(server) => server.reply(200, {
'notifications': {'method': method},
}),
);
}
// Pumps frames so the async config load (resolved via Dio's zero-duration
// timer) settles, for assertions that expect the toggle to be absent.
Future<void> settleConfig(WidgetTester tester) async {
for (var i = 0; i < 10; i++) {
await tester.pump(const Duration(milliseconds: 10));
}
}
testWidgets('shows the push toggle when the backend method is push', (
tester,
) async {
stubNotificationMethod('push');
await pumpScreen(tester, Settings(pushService: _FakePushService()));
await pumpUntilFound(tester, find.text(_toggleText));
expect(find.text(_toggleText), findsOneWidget);
});
testWidgets('hides the push toggle when push is unsupported', (tester) async {
stubNotificationMethod('push');
await pumpScreen(
tester,
Settings(pushService: _FakePushService(supported: false)),
);
await settleConfig(tester);
expect(find.text(_toggleText), findsNothing);
});
testWidgets('hides the push toggle when the backend method is not push', (
tester,
) async {
stubNotificationMethod('pushover');
await pumpScreen(tester, Settings(pushService: _FakePushService()));
await settleConfig(tester);
expect(find.text(_toggleText), findsNothing);
});
testWidgets('enabling push registers and shows a success message', (
tester,
) async {
stubNotificationMethod('push');
final service = _FakePushService(enableResult: true);
await pumpScreen(tester, Settings(pushService: service));
await pumpUntilFound(tester, find.byType(SwitchListTile));
await tester.tap(find.byType(SwitchListTile));
await pumpUntilFound(
tester,
find.text('Push notifications enabled on this device'),
);
expect(service.enableCalls, 1);
expect(PrefUtil.getValue('push_enabled', false), isTrue);
expect(
tester.widget<SwitchListTile>(find.byType(SwitchListTile)).value,
isTrue,
);
});
testWidgets('a declined permission leaves the toggle off with an error', (
tester,
) async {
stubNotificationMethod('push');
final service = _FakePushService(enableResult: false);
await pumpScreen(tester, Settings(pushService: service));
await pumpUntilFound(tester, find.byType(SwitchListTile));
await tester.tap(find.byType(SwitchListTile));
await pumpUntilFound(tester, find.textContaining('Could not enable push'));
expect(service.enableCalls, 1);
expect(
tester.widget<SwitchListTile>(find.byType(SwitchListTile)).value,
isFalse,
);
});
testWidgets('disabling push unregisters and shows a success message', (
tester,
) async {
stubNotificationMethod('push');
await PrefUtil.setValue('push_enabled', true);
final service = _FakePushService();
await pumpScreen(tester, Settings(pushService: service));
await pumpUntilFound(tester, find.byType(SwitchListTile));
// Starts on because the stored intent is enabled.
expect(
tester.widget<SwitchListTile>(find.byType(SwitchListTile)).value,
isTrue,
);
await tester.tap(find.byType(SwitchListTile));
await pumpUntilFound(
tester,
find.text('Push notifications disabled on this device'),
);
expect(service.disableCalls, 1);
expect(PrefUtil.getValue('push_enabled', false), isFalse);
});
testWidgets('hides the test button when push is off on this device', (
tester,
) async {
stubNotificationMethod('push');
await PrefUtil.setValue('push_enabled', false);
await pumpScreen(tester, Settings(pushService: _FakePushService()));
await pumpUntilFound(tester, find.byType(SwitchListTile));
expect(find.text('Send test notification'), findsNothing);
});
testWidgets('reports the device count when a test notification is sent', (
tester,
) async {
stubNotificationMethod('push');
adapter.onPost(
'/notifications/test',
(server) => server.reply(200, {'delivered': 2}),
);
await PrefUtil.setValue('push_enabled', true);
await pumpScreen(tester, Settings(pushService: _FakePushService()));
await pumpUntilFound(tester, find.text('Send test notification'));
await tester.tap(find.text('Send test notification'));
await pumpUntilFound(
tester,
find.text('Test notification sent to 2 devices.'),
);
});
testWidgets('warns when a test notification reaches no devices', (
tester,
) async {
stubNotificationMethod('push');
adapter.onPost(
'/notifications/test',
(server) => server.reply(200, {'delivered': 0}),
);
await PrefUtil.setValue('push_enabled', true);
await pumpScreen(tester, Settings(pushService: _FakePushService()));
await pumpUntilFound(tester, find.text('Send test notification'));
await tester.tap(find.text('Send test notification'));
await pumpUntilFound(
tester,
find.text('No devices are registered to receive push notifications.'),
);
});
}