fix(recommend): 首屏不再兜出重试空态
This commit is contained in:
1 parent
9117bf58b8
commit
d206465ee1
3 files changed
+302
-30
No files matched your search
@@ -11,6 +11,14 @@ import '../support/fake_catalog.dart';
|
||||
import '../support/fake_category.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_show_skeleton_before_feed_is_loaded', (tester) async {
|
||||
await _pumpRecommend(tester, active: false);
|
||||
|
||||
expect(find.byKey(const Key('recommend-feed-skeleton')), findsOneWidget);
|
||||
expect(find.byKey(const Key('recommend-feed-empty')), findsNothing);
|
||||
expect(find.text('Retry'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_refresh_once_when_parent_rebuilds_repeatedly', (
|
||||
tester,
|
||||
) async {
|
||||
@@ -52,6 +60,64 @@ void main() {
|
||||
expect(fixture.controller.refreshCalls, 2);
|
||||
});
|
||||
|
||||
testWidgets('should_auto_retry_three_times_then_show_network_error', (
|
||||
tester,
|
||||
) async {
|
||||
final fixture = await _pumpFailingRecommend(tester);
|
||||
|
||||
expect(fixture.controller.refreshCalls, 1);
|
||||
expect(find.byKey(const Key('recommend-feed-skeleton')), findsOneWidget);
|
||||
expect(find.byKey(const Key('recommend-feed-empty')), findsNothing);
|
||||
|
||||
for (var attempt = 0; attempt < 3; attempt++) {
|
||||
await tester.pump(const Duration(milliseconds: 1500));
|
||||
await tester.pump();
|
||||
expect(fixture.controller.refreshCalls, attempt + 2);
|
||||
if (attempt < 2) {
|
||||
expect(
|
||||
find.byKey(const Key('recommend-feed-skeleton')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byKey(const Key('recommend-feed-empty')), findsNothing);
|
||||
}
|
||||
}
|
||||
|
||||
final l10n = AppLocalizations.of(
|
||||
tester.element(find.byType(RecommendPage)),
|
||||
)!;
|
||||
expect(fixture.controller.refreshCalls, 4);
|
||||
expect(find.byKey(const Key('recommend-feed-skeleton')), findsNothing);
|
||||
expect(find.byKey(const Key('recommend-feed-empty')), findsOneWidget);
|
||||
expect(find.text(l10n.networkError), findsOneWidget);
|
||||
expect(find.text(l10n.retry), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_cancel_pending_auto_retry_when_disposed', (tester) async {
|
||||
final fixture = await _pumpFailingRecommend(tester);
|
||||
expect(fixture.controller.refreshCalls, 1);
|
||||
|
||||
await tester.pumpWidget(const MaterialApp(home: SizedBox.shrink()));
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
|
||||
expect(fixture.controller.refreshCalls, 1);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'should_show_no_stories_without_retry_when_loaded_feed_is_empty',
|
||||
(tester) async {
|
||||
final fixture = await _pumpEmptyRecommend(tester);
|
||||
|
||||
final l10n = AppLocalizations.of(
|
||||
tester.element(find.byType(RecommendPage)),
|
||||
)!;
|
||||
expect(fixture.controller.refreshCalls, 1);
|
||||
expect(find.byKey(const Key('recommend-feed-skeleton')), findsNothing);
|
||||
expect(find.byKey(const Key('recommend-feed-empty')), findsOneWidget);
|
||||
expect(find.text(l10n.noStories), findsOneWidget);
|
||||
expect(find.text(l10n.retry), findsNothing);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('should_not_refresh_after_dispose_when_channel_changes', (
|
||||
tester,
|
||||
) async {
|
||||
@@ -96,6 +162,58 @@ Future<_RecommendFixture> _pumpRecommend(
|
||||
return fixture;
|
||||
}
|
||||
|
||||
Future<_StatefulRecommendFixture> _pumpFailingRecommend(
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(390, 844);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
final catalog = FakeCatalogRepository();
|
||||
final controller = _StatefulHomeFeedController(catalog);
|
||||
final categories = CategoryController(
|
||||
FakeCategoryRepository(catalog: catalog),
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
addTearDown(categories.dispose);
|
||||
|
||||
final fixture = _StatefulRecommendFixture(
|
||||
controller: controller,
|
||||
categoryController: categories,
|
||||
);
|
||||
await tester.pumpWidget(fixture.app());
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
return fixture;
|
||||
}
|
||||
|
||||
Future<_StatefulRecommendFixture> _pumpEmptyRecommend(
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(390, 844);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
final catalog = FakeCatalogRepository();
|
||||
final controller = _StatefulHomeFeedController(catalog, fail: false);
|
||||
final categories = CategoryController(
|
||||
FakeCategoryRepository(catalog: catalog),
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
addTearDown(categories.dispose);
|
||||
|
||||
final fixture = _StatefulRecommendFixture(
|
||||
controller: controller,
|
||||
categoryController: categories,
|
||||
);
|
||||
await tester.pumpWidget(fixture.app());
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
return fixture;
|
||||
}
|
||||
|
||||
class _RecommendFixture {
|
||||
const _RecommendFixture({
|
||||
required this.controller,
|
||||
@@ -123,6 +241,32 @@ class _RecommendFixture {
|
||||
}
|
||||
}
|
||||
|
||||
class _StatefulRecommendFixture {
|
||||
const _StatefulRecommendFixture({
|
||||
required this.controller,
|
||||
required this.categoryController,
|
||||
});
|
||||
|
||||
final _StatefulHomeFeedController controller;
|
||||
final CategoryController categoryController;
|
||||
|
||||
Widget app() {
|
||||
return MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: ChangeNotifierProvider<CategoryController>.value(
|
||||
value: categoryController,
|
||||
child: RecommendPage(
|
||||
feedController: controller,
|
||||
active: true,
|
||||
videoOnly: true,
|
||||
onSearch: () {},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RecommendHarness extends StatefulWidget {
|
||||
const _RecommendHarness({
|
||||
super.key,
|
||||
@@ -181,3 +325,36 @@ class _RecordingHomeFeedController extends HomeFeedController {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class _StatefulHomeFeedController extends HomeFeedController {
|
||||
_StatefulHomeFeedController(super.repository, {this.fail = true});
|
||||
|
||||
final bool fail;
|
||||
int refreshCalls = 0;
|
||||
bool _loaded = false;
|
||||
bool _isLoading = false;
|
||||
String? _lastError;
|
||||
|
||||
@override
|
||||
bool get loaded => _loaded;
|
||||
|
||||
@override
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
@override
|
||||
String? get lastError => _lastError;
|
||||
|
||||
@override
|
||||
Future<bool> refreshCurrent() async {
|
||||
refreshCalls++;
|
||||
_isLoading = true;
|
||||
_lastError = null;
|
||||
notifyListeners();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
_isLoading = false;
|
||||
_loaded = true;
|
||||
if (fail) _lastError = 'network error';
|
||||
notifyListeners();
|
||||
return !fail;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user