CH-J SM Mobilequản lý máy chủ qua SSH
Menu
Mã nguồn đã công bố

CH-J SM Mobile

Duyệt thư mục và tệp của một bản phát hành ứng dụng cụ thể.

Tải mã nguồn dạng ZIP
CH-J / SM Mobile 175 tệp
sm-mobile-20261016T185235Z-8e93ecc1b59a Ngày thêm: · Ngày dựng:
adaptive_device_test.dart Ngày thêm: Chưa ghi nhận ngày 6,1 KB · 158 dòng
Mã Tải tệp xuống
1import 'dart:convert';
2import 'package:flutter/material.dart';
3import 'package:flutter_secure_storage/flutter_secure_storage.dart';
4import 'package:flutter_test/flutter_test.dart';
5import 'package:integration_test/integration_test.dart';
6import 'package:sm_mobile/core/app_controller.dart';
7import 'package:sm_mobile/main.dart';
8import 'package:sm_mobile/models/server_profile.dart';
9import 'package:sm_mobile/services/secure_store.dart';
10import 'package:sm_mobile/ui/terminal_page.dart';
12/// Native window changes on a real device, emulator or simulator.
13///
14/// After connecting to the isolated OpenSSH fixture the test watches the
15/// window for SM_ADAPTIVE_SECONDS. Rotate, fold, unfold, resize or split the
16/// window meanwhile (by hand, or by a script driving the emulator). Every new
17/// window shape must keep the same SSH client and terminal page, and the
18/// server's PTY must report the terminal's new rows and columns.
19void main() {
20 final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
21 const seconds = int.fromEnvironment('SM_ADAPTIVE_SECONDS', defaultValue: 90);
22 const minShapes = int.fromEnvironment('SM_ADAPTIVE_MIN_SHAPES');
23 testWidgets(
24 'native resize, rotation and folding keep the SSH session and PTY size',
25 (tester) async {
26 final namespace = 'sm_adaptive_${newId()}';
27 final store = DeviceStore(
28 FlutterSecureStorage(
29 aOptions: AndroidOptions(
30 resetOnError: false,
31 storageNamespace: namespace,
32 ),
33 iOptions: IOSOptions(
34 accountName: namespace,
35 accessibility: KeychainAccessibility.unlocked_this_device,
36 ),
37 ),
38 );
39 final c = AppController(store);
40 // Frames are produced by the real engine; waiting must not depend on
41 // them, because a hidden window (for example after folding) has none.
42 binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
43 Future<void> idle(int ms) =>
44 Future<void>.delayed(Duration(milliseconds: ms));
45 Future<void> until(bool Function() predicate, String what) async {
46 for (var i = 0; i < 300 && !predicate(); i++) {
47 await idle(100);
48 }
49 expect(predicate(), true, reason: what);
50 }
52 final lifecycle = AppLifecycleListener(
53 onStateChange: (state) {
54 // ignore: avoid_print
55 print('ADAPT lifecycle ${state.name}');
56 },
57 );
59 await c.initialize();
60 c.locale = const Locale('en');
61 // The background lock stays on: if folding or resizing were reported as
62 // leaving the app, it would lock, disconnect and fail this test.
63 await c.acceptLicense();
64 await c.openVault('native adaptive master password', create: true);
65 final profile = ServerProfile(
66 name: 'Isolated adaptive test',
67 host: const String.fromEnvironment(
68 'SM_TEST_HOST',
69 defaultValue: '127.0.0.1',
70 ),
71 username: const String.fromEnvironment('SM_TEST_USER'),
72 port: 22229,
73 auth: AuthMethod.privateKey,
74 );
75 const fingerprint = String.fromEnvironment('SM_TEST_FINGERPRINT');
76 await c.ssh.connect(
77 profile,
78 credential: utf8.decode(
79 base64Decode(const String.fromEnvironment('SM_TEST_KEY_B64')),
80 ),
81 passphrase: 'isolated-test-passphrase',
82 prompt: (_, _, shown) async => shown == fingerprint,
83 );
84 expect(c.ssh.error, null);
85 expect(c.ssh.connected, true);
86 final client = c.ssh.client, terminal = c.ssh.terminal;
88 await tester.pumpWidget(SmMobileApp(c));
89 await idle(1500);
90 await tester.tap(find.text('Terminal').last);
91 await idle(1500);
92 final page = tester.state(find.byType(TerminalPage));
94 String shape() {
95 final v = tester.view;
96 final s = v.physicalSize / v.devicePixelRatio;
97 return '${s.width.toStringAsFixed(1)}x${s.height.toStringAsFixed(1)}dp'
98 ' @${v.devicePixelRatio}x physical ${v.physicalSize.width.toInt()}'
99 'x${v.physicalSize.height.toInt()} features '
100 '${v.displayFeatures.map((f) => '${f.type.name}/${f.state.name}').join(',')}';
101 }
103 final seen = <String>{};
104 var probe = 0;
105 Future<void> check(String now) async {
106 if (!c.vault.unlocked || !c.ssh.connected) {
107 // ignore: avoid_print
108 print('ADAPT session lost at $now (locked: ${!c.vault.unlocked})');
109 }
110 expect(tester.takeException(), null, reason: now);
111 expect(c.ssh.connected, true, reason: now);
112 expect(c.ssh.client, same(client), reason: '$now: no reconnect');
113 expect(c.ssh.terminal, same(terminal), reason: now);
114 expect(tester.state(find.byType(TerminalPage)), same(page));
115 final rows = terminal.viewHeight, cols = terminal.viewWidth;
116 final step = probe++;
117 terminal.onOutput!(
118 "printf 'SZ%s_%s_\\n' $step \"\$(stty size | tr ' ' x)\"\r",
119 );
120 final expected = 'SZ${step}_${rows}x${cols}_';
121 await until(
122 () => terminal.buffer.getText().contains(expected),
123 '$now: remote PTY ${rows}x$cols',
124 );
125 // ignore: avoid_print
126 print('ADAPT ok $now terminal ${rows}x$cols');
127 }
129 var last = shape();
130 seen.add(last);
131 await check(last);
132 // ignore: avoid_print
133 print('ADAPT ready ${seconds}s');
134 final end = DateTime.now().add(const Duration(seconds: seconds));
135 while (DateTime.now().isBefore(end)) {
136 await idle(250);
137 final now = shape();
138 if (now == last && c.vault.unlocked) continue;
139 // Let the window settle before measuring the new terminal size.
140 await idle(800);
141 last = shape();
142 seen.add(last);
143 await check(last);
144 }
145 // ignore: avoid_print
146 print('ADAPT shapes ${seen.length}');
147 expect(seen.length, greaterThanOrEqualTo(minShapes));
149 lifecycle.dispose();
150 await tester.pumpWidget(const SizedBox());
151 c.dispose();
152 for (final key in ['vault', 'consent', 'preferences']) {
153 await store.delete(key);
154 }
155 },
156 timeout: const Timeout(Duration(seconds: seconds + 180)),
157 );
SHA-25646f35a640990680071f119f206f02c300c4f0863e9277ab7c69ae4b231a3006c

CH-J Proprietary Software License 1.14 · Mã nguồn được cung cấp theo CH-J Proprietary Software License 1.14. Việc công khai mã nguồn không thay đổi các điều khoản giấy phép và không cấp thêm quyền nào.

SHA-256 của tệp nén46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655
Tệp