From 5b5bfe33ba0a0fac92b5be3e5f320e4c6cfbbd7f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 29 May 2026 10:07:21 +0000 Subject: [PATCH] test(ensemble): cover progress timer dispose and ListView scroll APIs Add widget tests for EnsembleProgressIndicator countdown timer cleanup after dispose (regression guard for 07e1786b) and for ListViewController scrollToOffset/scrollToTop behavior added in scrollToOffset work. Co-authored-by: Sharjeel Yunus --- .../ensemble/test/widget/list_view_test.dart | 63 +++++++++++++++++++ .../test/widget/progress_indicator_test.dart | 34 ++++++++++ 2 files changed, 97 insertions(+) create mode 100644 modules/ensemble/test/widget/progress_indicator_test.dart diff --git a/modules/ensemble/test/widget/list_view_test.dart b/modules/ensemble/test/widget/list_view_test.dart index 132f7b174..31f32fd3e 100644 --- a/modules/ensemble/test/widget/list_view_test.dart +++ b/modules/ensemble/test/widget/list_view_test.dart @@ -19,6 +19,69 @@ void main() { return widget; } + ensemble.ListView listViewWithManyChildren({int count = 20}) { + final widget = ensemble.ListView(); + widget.initChildren( + children: List.generate( + count, + (i) => ViewUtil.buildModel({ + 'Text': {'text': 'Item $i'} + }, null), + ), + ); + return widget; + } + + test('scrollToOffset is a no-op without attached scroll clients', () { + final controller = ensemble.ListViewController(); + final scrollController = ScrollController(); + controller.scrollController = scrollController; + + expect(() => controller.scrollToOffset(100), returnsNormally); + + scrollController.dispose(); + }); + + testWidgets('scrollToOffset jumps to pixel offset when not animated', + (tester) async { + final list = listViewWithManyChildren(); + + await tester.pumpWidget(TestUtils.wrapTestWidgetWithScope( + SizedBox( + height: 200, + width: 300, + child: list, + ), + )); + await tester.pumpAndSettle(); + + list.controller.scrollToOffset(80, animated: false); + await tester.pump(); + + expect(list.controller.scrollController!.offset, 80); + }); + + testWidgets('scrollToTop moves list to offset zero', (tester) async { + final list = listViewWithManyChildren(); + + await tester.pumpWidget(TestUtils.wrapTestWidgetWithScope( + SizedBox( + height: 200, + width: 300, + child: list, + ), + )); + await tester.pumpAndSettle(); + + list.controller.scrollToOffset(80, animated: false); + await tester.pump(); + expect(list.controller.scrollController!.offset, greaterThan(0)); + + list.controller.scrollToTop(animated: false); + await tester.pump(); + expect(list.controller.scrollController!.offset, 0); + }); + testWidgets('disposes internally-created scroll controller', (tester) async { final widget = ensemble.ListView(); diff --git a/modules/ensemble/test/widget/progress_indicator_test.dart b/modules/ensemble/test/widget/progress_indicator_test.dart new file mode 100644 index 000000000..7761a12b2 --- /dev/null +++ b/modules/ensemble/test/widget/progress_indicator_test.dart @@ -0,0 +1,34 @@ +import 'package:ensemble/widget/progress_indicator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_utils.dart'; + +void main() { + testWidgets('cancels countdown timers when removed from tree', (tester) async { + final widget = EnsembleProgressIndicator(); + widget.setProperty('countdown', 2); + + await tester.pumpWidget(TestUtils.wrapTestWidgetWithScope(widget)); + await tester.pump(); + + await tester.pumpWidget(TestUtils.wrapTestWidget(const SizedBox.shrink())); + await tester.pump(); + + // Advance well past the countdown; leaked timers would call setState off-tree. + await tester.pump(const Duration(seconds: 5)); + expect(tester.takeException(), isNull); + }); + + testWidgets('countdown updates progress after periodic tick', (tester) async { + final widget = EnsembleProgressIndicator(); + widget.setProperty('countdown', 2); + + await tester.pumpWidget(TestUtils.wrapTestWidgetWithScope(widget)); + await tester.pump(); + + await tester.pump(const Duration(milliseconds: 150)); + expect(widget.controller.value, isNotNull); + expect(widget.controller.value, greaterThan(0)); + }); +}