Files
jiu/client/test/write_guard_test.dart
2026-06-24 23:12:20 +08:00

135 lines
4.4 KiB
Dart
Raw Permalink 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.
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<LicenseInfo?> 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);
});
}