import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:jiu_client/core/auth/auth_state.dart'; import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/models/shop.dart'; import 'package:jiu_client/providers/shop_provider.dart'; import 'package:jiu_client/providers/license_provider.dart'; import 'package:jiu_client/providers/update_provider.dart'; import 'package:jiu_client/providers/connectivity_provider.dart'; import 'package:jiu_client/providers/session_heartbeat.dart'; import 'package:jiu_client/screens/shell/app_shell.dart'; import 'package:jiu_client/widgets/app_status_bar.dart'; import 'golden_harness.dart'; /// 外壳挂载版 golden 骨架:把屏放进真 AppShell(顶栏+侧栏+状态栏)渲染, /// 与原型整页截图(fidelity.mjs 的 proto 基准)同构,可直接整屏 diff。 /// 门店/用户/版本对齐原型:鼎晟酒行/DSJH-001、王经理(管理员)、v1.0.72。 class _FakeAuth extends AuthNotifier { _FakeAuth() { state = const AuthState( user: AuthUser( accessToken: 't', refreshToken: 'r', id: 1, username: 'wang', realName: '王经理', shopNo: 'DSJH-001', shopId: 1, role: 'admin'), ); } } class _FakeUpdate extends UpdateNotifier { @override Future build() async => null; } class _FakeLicense extends LicenseNotifier { @override Future build() async => null; } const _shop = ShopInfo( id: 1, code: 'DSJH-001', name: '鼎晟酒行', address: '浙江省杭州市', phone: '0571-88886666', managerName: '王经理'); /// 外壳自身依赖的 provider 覆盖(认证/门店/授权/更新/网络/心跳/版本)。 List shellOverrides() => [ authStateProvider.overrideWith((ref) => _FakeAuth()), shopInfoProvider.overrideWith((ref) => _shop), licenseProvider.overrideWith(() => _FakeLicense()), updateProvider.overrideWith(() => _FakeUpdate()), // 跳过联网首检 / 不启动心跳,避免后台 Timer 泄漏 connectivityProvider .overrideWith((ref) => ConnectivityNotifier(skipInit: true)), sessionHeartbeatProvider.overrideWith((ref) => SessionHeartbeat(ref)), appVersionProvider.overrideWith((ref) => 'v1.0.72'), isReadonlyProvider.overrideWithValue(false), ]; // 外壳 nav 引用 9 个分支序号(入/出库·库存·财务·往来·基础数据·设备·设置·关于)。 StatefulShellBranch _branch(String path, [WidgetBuilder? builder]) => StatefulShellBranch(routes: [ GoRoute( path: path, builder: (ctx, _) => builder?.call(ctx) ?? const SizedBox.expand()), ]); GoRouter _router(String initialPath, WidgetBuilder screen) => GoRouter( initialLocation: initialPath, routes: [ StatefulShellRoute.indexedStack( builder: (ctx, state, shell) => AppShell(navigationShell: shell), branches: [ for (final p in const [ '/stock-in', '/stock-out', '/inventory', '/finance', '/partners', '/products', '/devices', '/settings', '/about', ]) _branch(p, p == initialPath ? screen : null), ], ), ], ); /// 在指定主题下把 [screen] 挂进外壳渲染并比对 golden(1280×900@2x 对齐原型)。 Future pumpShellGolden( WidgetTester tester, { required String path, required WidgetBuilder screen, required String theme, required String goldenName, List overrides = const [], Size logical = const Size(1280, 900), double dpr = 2.0, }) async { await ensureGoldenFonts(); // 冻结状态栏时钟,确定化 golden(与 app_shell golden 同基准时刻)。 AppStatusBar.clock = () => DateTime(2026, 7, 13, 9, 30, 15); AppStatusBar.ticking = false; tester.view.physicalSize = Size(logical.width * dpr, logical.height * dpr); tester.view.devicePixelRatio = dpr; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(ProviderScope( overrides: [...shellOverrides(), ...overrides], child: MaterialApp.router( debugShowCheckedModeBanner: false, theme: withGoldenFont(buildTheme(theme)), routerConfig: _router(path, screen), ), )); await tester.pumpAndSettle(); await expectLater( find.byType(MaterialApp), matchesGoldenFile('goldens/$goldenName.png'), ); } /// 「同一屏 × A/B/C 三主题」外壳挂载 golden 组。 void shellGoldenAcrossThemes( String description, { required String path, required WidgetBuilder screen, required String goldenPrefix, List Function()? overrides, Size logical = const Size(1280, 900), double dpr = 2.0, }) { for (final theme in const ['a', 'b', 'c']) { testWidgets('$description · theme $theme', (tester) async { await pumpShellGolden( tester, path: path, screen: screen, theme: theme, goldenName: '${goldenPrefix}_$theme', overrides: overrides?.call() ?? const [], logical: logical, dpr: dpr, ); }); } }