Files
photo-live/apps/mobile/test/app/app_shell_test.dart
T
浅念 955f9b13c1 feat(mobile): upload queue ui and continuous capture page
Task 9 (P3 plan): queue tab replaces the stub, continuous capture page
enqueues per shot, album detail gains an upload FAB, and the upload
engine lifecycle is now bound to the login session.

- Engine scope: session account drives UploadEngine start/stop; wired
  from app boot, login success, logout, and 401 eviction. Tasks stay in
  the store across logout (accountId hard isolation).
- Queue tab: album x state x time filter bar, task rows with thumbnail,
  state chip, localized error copy, attempt count, per-row progress via
  a broadcast progress hub, state-dependent actions (re-pick retry,
  retryNow, cancel, delete), batch retry bar, 200ms-debounced live
  refresh from drift table updates.
- Capture page: camera preview via injected CaptureSource, one enqueue
  per shutter press, persistent album/category/badges overlay with a
  switch-only-affects-new-shots hint, gallery multi-pick, exit confirm
  while uploads are pending, permission-denied and init-failure views.
- POST_NOTIFICATIONS runtime permission via a small MethodChannel
  (photolive/permissions) + PermissionPlugin.kt: single runtime
  permission need, avoids permission_handler dependency; requested once
  on first enqueue, silent degradation when denied or unavailable.
- Album detail FAB: capture-and-upload / pick-from-gallery sheet.
- l10n: ~50 new keys in zh/en arbs; tests: +45 (filter matrix, engine
  scope lifecycle, permission gate, queue tab, capture page, FAB entry);
  existing page tests updated to inject session/task-store fakes.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-19 04:47:31 +08:00

60 lines
2.2 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.
/// App 壳 401 接线测试(P3 计划 Task 3)。
///
/// 覆盖:业务端点 401 → ApiClient 清会话并触发 onUnauthorized →
/// 根导航清栈回 /login(PhotoLiveApp.initState 注册的回调)。
library;
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:photo_live/app/app.dart';
import 'package:photo_live/core/api_client.dart';
import 'package:photo_live/core/error_codes.dart';
import 'package:photo_live/core/session_store.dart';
import 'package:photo_live/pages/login_page.dart';
import '../helpers/fakes.dart';
void main() {
testWidgets('业务 401 → onUnauthorized 清栈回 /login', (tester) async {
final store = FakeSessionStore();
store.session = Session(
token: 'tok-live-1',
expiresAt: DateTime.utc(2099, 1, 1),
account: 'staff01',
);
final adapter = FakeAdapter(
(o) => (401, errorBody(ErrorCodes.authTokenExpired)),
);
final client = ApiClient(
sessionStore: store,
dio: Dio(BaseOptions(baseUrl: 'http://test.local'))
..httpClientAdapter = adapter,
);
// PhotoLiveApp.initState 读取被 override 的客户端并注册 onUnauthorized。
// Task 9 起根组件 watch 引擎作用域(会话→引擎 start),真实
// flutter_secure_storage/path_provider 平台通道在测试环境挂起,须替身。
await tester.pumpWidget(
ProviderScope(
overrides: [...pageTestOverrides(session: store, client: client)],
child: const PhotoLiveApp(),
),
);
await tester.pumpAndSettle();
// 先抵 /home 再触发 401:验证清栈(回 /login 而非叠加路由)。
rootNavigatorKey.currentState!
.pushNamedAndRemoveUntil('/home', (route) => false);
await tester.pumpAndSettle();
// 触发 401 但不在测试体内 await(fake-async 下时钟由 pump 推进,
// 阻塞等待会死锁);AppException 由 ignore 吞掉,断言在下方。
client.listAlbums().ignore();
await tester.pumpAndSettle();
expect(store.session, isNull, reason: '业务 401 清空本地会话');
expect(find.byType(LoginPage), findsOneWidget, reason: '清栈回登录页');
});
}