Files
pangolin/client/test/unit/settings_autoconnect_test.dart
wangjia 8584f0459e feat(client): 自动连接开关 + Windows 开机自启改任务计划程序
- 新增「自动连接」设置开关(默认关,持久化):启动 app 进登录态主界面后,等节点就绪
  自动连一次(_RootFlowState 一次性 listenManual nodesProvider → toggle())。全平台生效。
- Windows 开机自启改用 schtasks /rl highest 登录触发任务:本应用 requireAdministrator,
  原注册表 Run 键(非提权)开机会弹 UAC/起不来 → 任务计划静默提权拉起。macOS/Linux 仍用
  launch_at_startup 登录项。
- 设置页功能组加「自动连接」行;新增 autoConnect/autoConnectSub 文案。
- _load 加 mounted 守卫(异步初始化期间已 dispose 不再写 state)。
- 测试:autoConnect 默认关 / _load 读持久化 / setAutoConnect 落盘。

注:移动端真·常连(Android Always-on + iOS/iPad On-Demand,无 UI 自连)是更大的原生活,
已单列 todo #14 后续做;本轮移动端只有「打开 app 自动连接」。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:46:06 +08:00

60 lines
2.5 KiB
Dart

// settings_autoconnect_test.dart — 自动连接开关:默认关 + 改状态并持久化(重启恢复)。
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge_mock.dart';
import 'package:pangolin_vpn/bridge/vpn_bridge_provider.dart';
import 'package:pangolin_vpn/state/settings_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
SharedPreferences.setMockInitialValues({});
// 桌面(mac 测试机)构造时会跑 _applyAutostart → PackageInfo.fromPlatform;
// mock 掉避免无插件挂起、_load 拖到 dispose 之后报「used after dispose」。
PackageInfo.setMockInitialValues(
appName: 'Pangolin', packageName: 'com.pangolin',
version: '1.0.0', buildNumber: '1', buildSignature: '',
);
});
ProviderContainer makeContainer() => ProviderContainer(overrides: [
vpnBridgeProvider.overrideWithValue(VpnBridgeMock()),
]);
// 轮询等 _load() 把持久化值读进 state(异步,首次 SharedPreferences 初始化可能偏慢)。
Future<void> waitAutoConnect(ProviderContainer c, bool want) async {
for (var i = 0; i < 60; i++) {
if (c.read(settingsProvider).autoConnect == want) return;
await Future<void>.delayed(const Duration(milliseconds: 10));
}
}
test('autoConnect 默认关(const 默认 + 空存储加载)', () async {
expect(const AppSettings().autoConnect, isFalse);
final c = makeContainer();
addTearDown(c.dispose);
await waitAutoConnect(c, false);
expect(c.read(settingsProvider).autoConnect, isFalse);
});
test('_load 从持久化恢复 autoConnect=true', () async {
SharedPreferences.setMockInitialValues({'set_auto_connect': true});
final c = makeContainer();
addTearDown(c.dispose);
await waitAutoConnect(c, true);
expect(c.read(settingsProvider).autoConnect, isTrue, reason: '应读出持久化值');
});
test('setAutoConnect 写入持久化存储', () async {
final c = makeContainer();
addTearDown(c.dispose);
await waitAutoConnect(c, false); // 等初次加载安定
await c.read(settingsProvider.notifier).setAutoConnect(true);
// 校验落盘(状态可能被并发的初次 _load 覆盖,但存储一定被写入)。
final p = await SharedPreferences.getInstance();
expect(p.getBool('set_auto_connect'), isTrue, reason: '应持久化');
});
}