Files
pangolin/client/test/unit/nodes_provider_test.dart
T
wangjia 4dc4127252 feat(client): 三端布局架构 + macOS 桌面端 + app 图标
三端布局(mobile/tablet/desktop):
- core/responsive/form_factor.dart 形态判定 + shell/ 分发器(home_shell→desktop/mobile)
- desktop_shell 对照 ui_kits/desktop/dapp.jsx: 侧栏204·6项 + 套餐卡 + 顶栏(标题/状态/主题切换) + 连接页居中单列
- 新增组件 nav_sidebar / plan_badge_card / content_top_bar / bottom_tab_bar
- 新增一级页 contact_page / settings_page; navigation_provider(NavView)
- 删除旧 widgets/home_shell.dart(逻辑迁入 shell/)

macOS 桌面端:
- 窗口默认 920×600 + 最小 720×560(MainFlutterWindow.swift)
- app 图标替换为穿山甲(AppIcon.appiconset 全套, 由 app-icon.svg 渲染)

其余(本会话):
- Phase2 接线: auth_api/token_store/auth_provider/vpn_bridge_provider + 真实 connection/nodes
- lucide_icons 兼容补丁(packages/lucide_icons_patched) 修复 IconData final 报错
- 测试修复: connect_passthrough(UTF-8) / harness / golden @Skip
- l10n 新增 settingsTitle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:23:20 +08:00

68 lines
2.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// nodes_provider_test.dart — 节点选择 / 智能选择派生
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pangolin_vpn/l10n/app_text.dart';
import 'package:pangolin_vpn/models/node.dart';
import 'package:pangolin_vpn/services/token_store.dart';
import 'package:pangolin_vpn/state/auth_provider.dart';
import 'package:pangolin_vpn/state/nodes_provider.dart';
// ── Stub TokenStore(无平台依赖)──────────────────────────────────
class _NullTokenStore implements TokenStore {
const _NullTokenStore();
@override
Future<void> saveTokens({required String access, required String refresh}) async {}
@override
Future<String?> loadAccessToken() async => null;
@override
Future<String?> loadRefreshToken() async => null;
@override
Future<void> clear() async {}
}
// ── 辅助:带 stub 覆盖的 ProviderContainer ──────────────────────
ProviderContainer makeContainer({List<Override> overrides = const []}) =>
ProviderContainer(overrides: [
tokenStoreProvider.overrideWithValue(const _NullTokenStore()),
// effectiveNodeProvider 在 nodesProvider 加载时退回 kDemoNodes
// 所以测试直接读 effectiveNodeProvider,无需等待 nodesProvider.future。
...overrides,
]);
void main() {
test('默认智能选择(AUTO)', () {
final c = makeContainer();
addTearDown(c.dispose);
expect(c.read(selectedNodeCodeProvider), kSmartNodeCode);
expect(c.read(isSmartSelectProvider), true);
});
test('智能选择取延迟最小节点(退回 kDemoNodes)', () {
final c = makeContainer();
addTearDown(c.dispose);
// nodesProvider 加载中时退回 kDemoNodeseffectiveNodeProvider 立即可用。
final node = c.read(effectiveNodeProvider);
final minPing = kDemoNodes.map((n) => n.ping).reduce((a, b) => a < b ? a : b);
expect(node.ping, minPing);
});
test('选中具体节点后生效节点随之改变', () {
final c = makeContainer();
addTearDown(c.dispose);
c.read(selectedNodeCodeProvider.notifier).state = 'JP';
expect(c.read(isSmartSelectProvider), false);
expect(c.read(effectiveNodeProvider).code, 'JP');
});
test('localizedSub 不串语言:无标签用拉丁地名', () {
const tw = Node(code: 'TW', nameZh: '台湾 台北', nameEn: 'Taipei', ping: 28);
expect(tw.localizedSub(AppLang.zh), 'Taipei');
expect(tw.localizedSub(AppLang.en), 'Taipei');
const hk = Node(code: 'HK', nameZh: '香港 · 流媒体', nameEn: 'Hong Kong', ping: 18, tag: NodeTag.streaming);
expect(hk.localizedSub(AppLang.zh), '流媒体优化');
expect(hk.localizedSub(AppLang.en), 'Streaming');
});
}