import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jiu_client/core/auth/auth_state.dart'; import 'package:jiu_client/core/config/license_copy.dart'; import 'package:jiu_client/core/theme/themes.dart'; import 'package:jiu_client/models/license.dart'; import 'package:jiu_client/providers/license_provider.dart'; import 'package:jiu_client/widgets/write_guard.dart'; /// 用固定 LicenseInfo(含指定 phase)填充 licenseProvider 的 Fake Notifier。 class _FakeLicenseNotifier extends LicenseNotifier { final LicenseInfo? _value; _FakeLicenseNotifier(this._value); @override Future build() async => _value; } LicenseInfo _lic(String phase, {int expiredDays = 0}) => LicenseInfo( id: 1, type: 'annual', isActive: true, maxDevices: 3, phase: phase, expiresAt: DateTime.now().subtract(Duration(days: expiredDays)), ); /// 把一个带 onPressed 的按钮包进 WriteGuard,按 role + license phase 覆写 provider。 Widget _harness({ required bool readonly, LicenseInfo? lic, required VoidCallback onPressed, }) { return ProviderScope( overrides: [ isReadonlyProvider.overrideWithValue(readonly), licenseProvider.overrideWith(() => _FakeLicenseNotifier(lic)), ], child: MaterialApp( theme: buildTheme('a'), home: Scaffold( body: WriteGuard( child: ElevatedButton( onPressed: onPressed, child: const Text('新建'), ), ), ), ), ); } void main() { testWidgets('只读角色:按钮隐藏(findsNothing)', (tester) async { var tapped = false; await tester.pumpWidget( _harness(readonly: true, lic: _lic('normal'), onPressed: () => tapped = true), ); await tester.pumpAndSettle(); expect(find.text('新建'), findsNothing); expect(tapped, isFalse); }); testWidgets('operator + normal:按钮可见且可点', (tester) async { var tapped = false; await tester.pumpWidget( _harness(readonly: false, lic: _lic('normal'), onPressed: () => tapped = true), ); await tester.pumpAndSettle(); expect(find.text('新建'), findsOneWidget); await tester.tap(find.text('新建')); await tester.pump(); expect(tapped, isTrue); }); testWidgets('operator + grace(宽限期):按钮可见且可点', (tester) async { var tapped = false; await tester.pumpWidget( _harness( readonly: false, lic: _lic('grace', expiredDays: 3), onPressed: () => tapped = true, ), ); await tester.pumpAndSettle(); expect(find.text('新建'), findsOneWidget); await tester.tap(find.text('新建')); await tester.pump(); expect(tapped, isTrue); }); testWidgets('operator + readonly(过期>7天):按钮可见但禁用,点击弹提示且不触发 onPressed', (tester) async { var tapped = false; final lic = _lic('readonly', expiredDays: 10); await tester.pumpWidget( _harness(readonly: false, lic: lic, onPressed: () => tapped = true), ); await tester.pumpAndSettle(); // 按钮仍渲染(不隐藏),但被 IgnorePointer + Opacity 包裹禁用。 expect(find.text('新建'), findsOneWidget); expect(find.byType(IgnorePointer), findsWidgets); expect(find.byType(Opacity), findsWidgets); // 点击覆盖层弹出原因提示,且 onPressed 未被调用。 // 覆盖层(GestureDetector)盖在按钮上,命中的是覆盖层而非按钮文字,故 warnIfMissed:false。 await tester.tap(find.text('新建'), warnIfMissed: false); await tester.pumpAndSettle(); expect(tapped, isFalse); expect(find.text(LicenseCopy.writeBlockedToast(lic)), findsOneWidget); }); testWidgets('operator + locked(过期>15天):按钮可见但禁用,弹锁定提示', (tester) async { var tapped = false; final lic = _lic('locked', expiredDays: 20); await tester.pumpWidget( _harness(readonly: false, lic: lic, onPressed: () => tapped = true), ); await tester.pumpAndSettle(); expect(find.text('新建'), findsOneWidget); await tester.tap(find.text('新建'), warnIfMissed: false); await tester.pumpAndSettle(); expect(tapped, isFalse); expect(find.text(LicenseCopy.writeBlockedToast(lic)), findsOneWidget); }); }