@@ -43,12 +43,10 @@ Future<void> main() async {
4343 ),
4444 );
4545
46- final playArrow = find.byIcon (Icons .play_arrow);
47- await tester.tap (playArrow);
48- await tester.runAsync (() => Future .delayed (Duration .zero));
46+ await tester.tapWithAsyncDelay (find.byIcon (Icons .play_arrow));
4947 expect (
5048 commands,
51- equals ([
49+ equals (const [
5250 Tuple2 (CommandType .setVolume, 1.0 ),
5351 Tuple2 (CommandType .play, null ),
5452 Tuple2 (CommandType .load, src),
@@ -71,13 +69,11 @@ Future<void> main() async {
7169 );
7270 await tester.runAsync (() => Future .delayed (Duration .zero));
7371
74- // force a widget tree disposal
75- await tester.pumpWidget (const SizedBox .shrink ());
76- await tester.pumpAndSettle ();
72+ await tester.cleanupWidget ();
7773
7874 expect (
7975 commands,
80- containsAll ([
76+ containsAll (const [
8177 Tuple2 (CommandType .pause, null ),
8278 Tuple2 (CommandType .seek, Duration .zero),
8379 ]),
@@ -99,30 +95,9 @@ Future<void> main() async {
9995 );
10096 expect (find.text ('-0:00' ), findsOneWidget);
10197
102- await tester.pumpAndSettle ( );
98+ await tester.waitForTextUpdate ( '-12:34' );
10399
104- // Wait for the duration stream to update by polling
105- await tester.runAsync (() async {
106- for (int i = 0 ; i < 10 ; i++ ) {
107- await Future .delayed (const Duration (milliseconds: 50 ));
108- await tester.pumpAndSettle ();
109-
110- // Check if the duration text has updated
111- final textWidgets = find.byType (Text );
112- if (textWidgets.evaluate ().isNotEmpty) {
113- final text = tester.widget <Text >(textWidgets.first);
114- if (text.data == '-12:34' ) {
115- return ; // Success - duration has updated
116- }
117- }
118- }
119- });
120-
121- expect (find.text ('-12:34' ), findsOneWidget);
122-
123- // force a widget tree disposal
124- await tester.pumpWidget (const SizedBox .shrink ());
125- await tester.pumpAndSettle ();
100+ await tester.cleanupWidget ();
126101 });
127102
128103 testWidgets ('shows position & duration (wide)' , (tester) async {
@@ -137,26 +112,9 @@ Future<void> main() async {
137112 );
138113 expect (find.text ('0:00 / 0:00' ), findsOneWidget);
139114
140- await tester.pumpAndSettle ();
141-
142- // Wait for the duration stream to update by polling
143- await tester.runAsync (() async {
144- for (int i = 0 ; i < 10 ; i++ ) {
145- await Future .delayed (const Duration (milliseconds: 50 ));
146- await tester.pumpAndSettle ();
147-
148- // Check if the duration text has updated
149- if (find.text ('0:00 / 12:34' ).evaluate ().isNotEmpty) {
150- return ; // Success - duration has updated
151- }
152- }
153- });
115+ await tester.waitForTextUpdate ('0:00 / 12:34' );
154116
155- expect (find.text ('0:00 / 12:34' ), findsOneWidget);
156-
157- // force a widget tree disposal
158- await tester.pumpWidget (const SizedBox .shrink ());
159- await tester.pumpAndSettle ();
117+ await tester.cleanupWidget ();
160118 });
161119
162120 testWidgets ('seeks' , (tester) async {
@@ -169,40 +127,23 @@ Future<void> main() async {
169127 ),
170128 ),
171129 );
172- await tester.pumpAndSettle ();
173-
174- // Wait for the duration stream to update by polling
175- await tester.runAsync (() async {
176- for (int i = 0 ; i < 10 ; i++ ) {
177- await Future .delayed (const Duration (milliseconds: 50 ));
178- await tester.pumpAndSettle ();
179130
180- // Check if the duration text has updated
181- if (find.text ('0:00 / 1:40' ).evaluate ().isNotEmpty) {
182- return ; // Success - duration has updated
183- }
184- }
185- });
186-
187- expect (find.text ('0:00 / 1:40' ), findsOneWidget);
131+ await tester.waitForTextUpdate ('0:00 / 1:40' );
188132 expect (
189133 commands,
190- equals ([
134+ equals (const [
191135 Tuple2 (CommandType .setVolume, 1.0 ),
192136 Tuple2 (CommandType .load, src),
193137 ]),
194138 );
195139 commands.clear ();
196140
197- await tester.tap (find.byType (Slider ));
198- await tester.runAsync (() => Future .delayed (Duration .zero));
141+ await tester.tapWithAsyncDelay (find.byType (Slider ));
199142 await tester.pumpAndSettle ();
200143 expect (find.text ('0:50 / 1:40' ), findsOneWidget);
201144 expect (commands, equals ([Tuple2 (CommandType .seek, duration * .5 )]));
202145
203- // force a widget tree disposal
204- await tester.pumpWidget (const SizedBox .shrink ());
205- await tester.pumpAndSettle ();
146+ await tester.cleanupWidget ();
206147 });
207148
208149 group ('mute' , () {
@@ -221,16 +162,13 @@ Future<void> main() async {
221162 await tester.pumpAndSettle ();
222163 commands.clear ();
223164
224- await tester.tap (find.byIcon (iconOn));
225- await tester.runAsync (() => Future .delayed (Duration .zero));
165+ await tester.tapWithAsyncDelay (find.byIcon (iconOn));
226166 await tester.pumpAndSettle ();
227167
228- expect (commands, equals ([Tuple2 (CommandType .setVolume, 0.0 )]));
168+ expect (commands, equals (const [Tuple2 (CommandType .setVolume, 0.0 )]));
229169 expect (find.byIcon (iconOff), findsOneWidget);
230170
231- // force a widget tree disposal
232- await tester.pumpWidget (const SizedBox .shrink ());
233- await tester.pumpAndSettle ();
171+ await tester.cleanupWidget ();
234172 });
235173
236174 testWidgets ('shows muted and unmutes' , (tester) async {
@@ -242,34 +180,16 @@ Future<void> main() async {
242180 ),
243181 );
244182
245- await tester.runAsync (() => Future .delayed (Duration .zero));
246- await tester.pumpAndSettle ();
247-
248- // Wait for the loading to complete before clearing commands
249- await tester.runAsync (() async {
250- for (int i = 0 ; i < 10 ; i++ ) {
251- await Future .delayed (const Duration (milliseconds: 50 ));
252- await tester.pumpAndSettle ();
253-
254- // Check if loading has completed by looking for load command
255- if (commands.any ((cmd) => cmd.item1 == CommandType .load)) {
256- break ;
257- }
258- }
259- });
260-
183+ await tester.waitForCommandUpdate (CommandType .load);
261184 commands.clear ();
262185
263- await tester.tap (find.byIcon (iconOff));
264- await tester.runAsync (() => Future .delayed (Duration .zero));
186+ await tester.tapWithAsyncDelay (find.byIcon (iconOff));
265187 await tester.pumpAndSettle ();
266188
267- expect (commands, equals ([Tuple2 (CommandType .setVolume, 1.0 )]));
189+ expect (commands, equals (const [Tuple2 (CommandType .setVolume, 1.0 )]));
268190 expect (find.byIcon (iconOn), findsOneWidget);
269191
270- // force a widget tree disposal
271- await tester.pumpWidget (const SizedBox .shrink ());
272- await tester.pumpAndSettle ();
192+ await tester.cleanupWidget ();
273193 });
274194 });
275195
@@ -309,3 +229,46 @@ Future<void> main() async {
309229 );
310230 });
311231}
232+
233+ extension on WidgetTester {
234+ /// Wait for text to appear with simplified polling
235+ Future <void > waitForTextUpdate (String expectedText) async {
236+ await pumpAndSettle ();
237+ await runAsync (() async {
238+ for (int i = 0 ; i < 10 ; i++ ) {
239+ await Future .delayed (const Duration (milliseconds: 50 ));
240+ await pumpAndSettle ();
241+ if (find.text (expectedText).evaluate ().isNotEmpty) {
242+ return ;
243+ }
244+ }
245+ });
246+ }
247+
248+ /// Wait for commands with simplified polling
249+ Future <void > waitForCommandUpdate (CommandType commandType) async {
250+ await runAsync (() => Future .delayed (Duration .zero));
251+ await pumpAndSettle ();
252+ await runAsync (() async {
253+ for (int i = 0 ; i < 10 ; i++ ) {
254+ await Future .delayed (const Duration (milliseconds: 50 ));
255+ await pumpAndSettle ();
256+ if (commands.any ((cmd) => cmd.item1 == commandType)) {
257+ break ;
258+ }
259+ }
260+ });
261+ }
262+
263+ /// Tap with async delay - for interactions that trigger async operations
264+ Future <void > tapWithAsyncDelay (Finder finder) async {
265+ await tap (finder);
266+ await runAsync (() => Future .delayed (Duration .zero));
267+ }
268+
269+ /// Standard cleanup
270+ Future <void > cleanupWidget () async {
271+ await pumpWidget (const SizedBox .shrink ());
272+ await pumpAndSettle ();
273+ }
274+ }
0 commit comments