@@ -2,6 +2,7 @@ import 'dart:convert';
2
2
3
3
import 'package:chessground/chessground.dart' ;
4
4
import 'package:dartchess/dartchess.dart' ;
5
+ import 'package:flutter/cupertino.dart' ;
5
6
import 'package:flutter/material.dart' ;
6
7
import 'package:flutter_riverpod/flutter_riverpod.dart' ;
7
8
import 'package:flutter_test/flutter_test.dart' ;
@@ -132,6 +133,95 @@ void main() {
132
133
});
133
134
});
134
135
136
+ group ('Game action negotiation' , () {
137
+ testWidgets ('takeback' , (WidgetTester tester) async {
138
+ await createTestGame (
139
+ tester,
140
+ pgn: 'e4 e5 Nf3' ,
141
+ clock: const (
142
+ running: true ,
143
+ initial: Duration (minutes: 1 ),
144
+ increment: Duration .zero,
145
+ white: Duration (seconds: 58 ),
146
+ black: Duration (seconds: 54 ),
147
+ emerg: Duration (seconds: 10 ),
148
+ ),
149
+ );
150
+ expect (find.byType (Chessboard ), findsOneWidget);
151
+ expect (find.byType (PieceWidget ), findsNWidgets (32 ));
152
+
153
+ // black plays
154
+ sendServerSocketMessages (testGameSocketUri, [
155
+ '{"t": "move", "v": 1, "d": {"ply": 4, "uci": "b8c6", "san": "Nc6", "clock": {"white": 58, "black": 52}}}' ,
156
+ ]);
157
+ await tester.pump (const Duration (milliseconds: 500 ));
158
+ expect (find.byKey (const Key ('c6-blackknight' )), findsOneWidget);
159
+ expect (
160
+ tester.widgetList <Clock >(find.byType (Clock )).last.active,
161
+ true ,
162
+ reason: 'white clock is active' ,
163
+ );
164
+ // white clock ticking
165
+ await tester.pump (const Duration (seconds: 1 ));
166
+ expect (findClockWithTime (Side .white, '0:56' ), findsOneWidget);
167
+ await tester.pump (const Duration (seconds: 1 ));
168
+ expect (findClockWithTime (Side .white, '0:55' ), findsOneWidget);
169
+
170
+ // black asks for takeback
171
+ sendServerSocketMessages (testGameSocketUri, [
172
+ '{"t":"takebackOffers","v":2,"d":{"black":true}}' ,
173
+ ]);
174
+ await tester.pump (const Duration (milliseconds: 1 ));
175
+
176
+ // see takeback button
177
+ expect (find.byIcon (CupertinoIcons .arrowshape_turn_up_left), findsOneWidget);
178
+ await tester.tap (find.byIcon (CupertinoIcons .arrowshape_turn_up_left));
179
+ // wait for the popup to show (cannot use pumpAndSettle because of clocks)
180
+ await tester.pump (const Duration (milliseconds: 100 ));
181
+ await tester.tap (find.text ('Accept' ));
182
+ await tester.pump (const Duration (milliseconds: 10 ));
183
+ // server acknowledges the takeback and ask client to reload
184
+ sendServerSocketMessages (testGameSocketUri, ['{"v": 3}' , '{"t":"reload","v":4,"d":null}' ]);
185
+ // wait for client to reconnect
186
+ await tester.pump (const Duration (milliseconds: 1 ));
187
+ // socket will reconnect, wait for connection
188
+ await tester.pump (kFakeWebSocketConnectionLag);
189
+ // server sends 'full' event immediately after reconnect
190
+ sendServerSocketMessages (testGameSocketUri, [
191
+ makeFullEvent (
192
+ const GameId ('qVChCOTc' ),
193
+ 'e4 e5 Nf3' ,
194
+ whiteUserName: 'Peter' ,
195
+ blackUserName: 'Steven' ,
196
+ youAre: Side .white,
197
+ socketVersion: 5 ,
198
+ clock: (
199
+ running: true ,
200
+ initial: const Duration (minutes: 1 ),
201
+ increment: Duration .zero,
202
+ white: const Duration (seconds: 55 ),
203
+ black: const Duration (seconds: 53 ),
204
+ emerg: const Duration (seconds: 10 ),
205
+ ),
206
+ ),
207
+ ]);
208
+ await tester.pump (const Duration (milliseconds: 1 ));
209
+
210
+ // black move is cancelled
211
+ expect (find.byKey (const Key ('c6-blackknight' )), findsNothing);
212
+ expect (find.byKey (const Key ('b8-blackknight' )), findsOneWidget);
213
+ expect (tester.widget <Clock >(findClock (Side .black)).active, true );
214
+ expect (tester.widget <Clock >(findClock (Side .white)).active, false );
215
+ // black clock is ticking
216
+ await tester.pump (const Duration (seconds: 1 ));
217
+ expect (findClockWithTime (Side .black, '0:52' ), findsOneWidget);
218
+ expect (findClockWithTime (Side .white, '0:55' ), findsOneWidget);
219
+ await tester.pump (const Duration (seconds: 1 ));
220
+ expect (findClockWithTime (Side .black, '0:51' ), findsOneWidget);
221
+ expect (findClockWithTime (Side .white, '0:55' ), findsOneWidget);
222
+ });
223
+ });
224
+
135
225
group ('Castling' , () {
136
226
const String castlingSetupPgn = 'e4 e5 Nf3 Nf6 Bc4 Bc5 d3 d6 Bd2 Bd7 Nc3 Nc6 Qe2 Qe7' ;
137
227
@@ -287,7 +377,8 @@ void main() {
287
377
group ('Clock' , () {
288
378
testWidgets ('loads on game start' , (WidgetTester tester) async {
289
379
await createTestGame (tester);
290
- expect (findClockWithTime ('3:00' ), findsNWidgets (2 ));
380
+ expect (findClockWithTime (Side .white, '3:00' ), findsOneWidget);
381
+ expect (findClockWithTime (Side .black, '3:00' ), findsOneWidget);
291
382
expect (
292
383
tester
293
384
.widgetList <Clock >(find.byType (Clock ))
@@ -300,7 +391,8 @@ void main() {
300
391
301
392
testWidgets ('ticks after the first full move' , (WidgetTester tester) async {
302
393
await createTestGame (tester);
303
- expect (findClockWithTime ('3:00' ), findsNWidgets (2 ));
394
+ expect (findClockWithTime (Side .white, '3:00' ), findsOneWidget);
395
+ expect (findClockWithTime (Side .black, '3:00' ), findsOneWidget);
304
396
await playMove (tester, 'e2' , 'e4' );
305
397
// at that point clock is not yet started
306
398
expect (
@@ -316,15 +408,11 @@ void main() {
316
408
'{"t": "move", "v": 2, "d": {"ply": 2, "uci": "e7e5", "san": "e5", "clock": {"white": 180, "black": 180}}}' ,
317
409
]);
318
410
await tester.pump (const Duration (milliseconds: 10 ));
319
- expect (
320
- tester.widgetList <Clock >(find.byType (Clock )).last.active,
321
- true ,
322
- reason: 'my clock is now active' ,
323
- );
411
+ expect (tester.widget <Clock >(findClock (Side .white)).active, true );
324
412
await tester.pump (const Duration (seconds: 1 ));
325
- expect (findClockWithTime ('2:59' ), findsOneWidget);
413
+ expect (findClockWithTime (Side .white, '2:59' ), findsOneWidget);
326
414
await tester.pump (const Duration (seconds: 1 ));
327
- expect (findClockWithTime ('2:58' ), findsOneWidget);
415
+ expect (findClockWithTime (Side .white, '2:58' ), findsOneWidget);
328
416
});
329
417
330
418
testWidgets ('ticks immediately when resuming game' , (WidgetTester tester) async {
@@ -340,17 +428,13 @@ void main() {
340
428
emerg: Duration (seconds: 30 ),
341
429
),
342
430
);
343
- expect (
344
- tester.widgetList <Clock >(find.byType (Clock )).first.active,
345
- true ,
346
- reason: 'black clock is already active' ,
347
- );
348
- expect (findClockWithTime ('2:58' ), findsOneWidget);
349
- expect (findClockWithTime ('2:54' ), findsOneWidget);
431
+ expect (tester.widget <Clock >(findClock (Side .black)).active, true );
432
+ expect (findClockWithTime (Side .white, '2:58' ), findsOneWidget);
433
+ expect (findClockWithTime (Side .black, '2:54' ), findsOneWidget);
350
434
await tester.pump (const Duration (seconds: 1 ));
351
- expect (findClockWithTime ('2:53' ), findsOneWidget);
435
+ expect (findClockWithTime (Side .black, '2:53' ), findsOneWidget);
352
436
await tester.pump (const Duration (seconds: 1 ));
353
- expect (findClockWithTime ('2:52' ), findsOneWidget);
437
+ expect (findClockWithTime (Side .black, '2:52' ), findsOneWidget);
354
438
});
355
439
356
440
testWidgets ('switch timer side after a move' , (WidgetTester tester) async {
@@ -366,43 +450,44 @@ void main() {
366
450
emerg: Duration (seconds: 30 ),
367
451
),
368
452
);
369
- expect (tester.widgetList <Clock >(find. byType ( Clock )).last .active, true );
453
+ expect (tester.widget <Clock >(findClock ( Side .white)) .active, true );
370
454
// simulates think time of 3s
371
455
await tester.pump (const Duration (seconds: 3 ));
372
456
await playMove (tester, 'g1' , 'f3' );
373
- expect (findClockWithTime ('2:55' ), findsOneWidget);
457
+ expect (findClockWithTime (Side .white, '2:55' ), findsOneWidget);
374
458
expect (
375
- tester.widgetList <Clock >(find. byType ( Clock )).last .active,
459
+ tester.widget <Clock >(findClock ( Side .white)) .active,
376
460
false ,
377
461
reason: 'white clock is stopped while waiting for server ack' ,
378
462
);
379
463
expect (
380
- tester.widgetList <Clock >(find. byType ( Clock )).first .active,
464
+ tester.widget <Clock >(findClock ( Side .black)) .active,
381
465
true ,
382
466
reason: 'black clock is now active but not yet ticking' ,
383
467
);
384
- expect (findClockWithTime ('3:00' ), findsOneWidget);
468
+ expect (findClockWithTime (Side .black, '3:00' ), findsOneWidget);
385
469
// simulates a long lag just to show the clock is not running yet
386
470
await tester.pump (const Duration (milliseconds: 200 ));
387
- expect (findClockWithTime ('3:00' ), findsOneWidget);
471
+ expect (findClockWithTime (Side .black, '3:00' ), findsOneWidget);
388
472
// server ack having the white clock updated with the increment
389
473
sendServerSocketMessages (testGameSocketUri, [
390
474
'{"t": "move", "v": 1, "d": {"ply": 3, "uci": "g1f3", "san": "Nf3", "clock": {"white": 177, "black": 180}}}' ,
391
475
]);
392
476
await tester.pump (const Duration (milliseconds: 10 ));
393
477
// we see now the white clock has got its increment
394
- expect (findClockWithTime ('2:57' ), findsOneWidget);
478
+ expect (findClockWithTime (Side .white, '2:57' ), findsOneWidget);
395
479
await tester.pump (const Duration (milliseconds: 100 ));
396
480
// black clock is ticking
397
- expect (findClockWithTime ('2:59' ), findsOneWidget);
481
+ expect (findClockWithTime (Side .black, '2:59' ), findsOneWidget);
398
482
await tester.pump (const Duration (seconds: 1 ));
399
- expect (findClockWithTime ('2:57' ), findsOneWidget);
400
- expect (findClockWithTime ('2:58' ), findsOneWidget);
483
+ expect (findClockWithTime (Side .white, '2:57' ), findsOneWidget);
484
+ expect (findClockWithTime (Side .black, '2:58' ), findsOneWidget);
401
485
await tester.pump (const Duration (seconds: 1 ));
402
- expect (findClockWithTime ('2:57' ), findsNWidgets (2 ));
486
+ expect (findClockWithTime (Side .white, '2:57' ), findsOneWidget);
487
+ expect (findClockWithTime (Side .black, '2:57' ), findsOneWidget);
403
488
await tester.pump (const Duration (seconds: 1 ));
404
- expect (findClockWithTime ('2:57' ), findsOneWidget);
405
- expect (findClockWithTime ('2:56' ), findsOneWidget);
489
+ expect (findClockWithTime (Side .white, '2:57' ), findsOneWidget);
490
+ expect (findClockWithTime (Side .black, '2:56' ), findsOneWidget);
406
491
});
407
492
408
493
testWidgets ('compensates opponent lag' , (WidgetTester tester) async {
@@ -436,15 +521,15 @@ void main() {
436
521
socketVersion: ++ socketVersion,
437
522
);
438
523
// black clock is active
439
- expect (tester.widgetList <Clock >(find. byType ( Clock )).first .active, true );
440
- expect (findClockWithTime ('0:54' ), findsOneWidget);
524
+ expect (tester.widget <Clock >(findClock ( Side .black)) .active, true );
525
+ expect (findClockWithTime (Side .black, '0:54' ), findsOneWidget);
441
526
await tester.pump (const Duration (milliseconds: 250 ));
442
527
// lag is 250ms, so clock will only start after that delay
443
- expect (findClockWithTime ('0:54' ), findsOneWidget);
528
+ expect (findClockWithTime (Side .black, '0:54' ), findsOneWidget);
444
529
await tester.pump (const Duration (milliseconds: 100 ));
445
- expect (findClockWithTime ('0:53' ), findsOneWidget);
530
+ expect (findClockWithTime (Side .black, '0:53' ), findsOneWidget);
446
531
await tester.pump (const Duration (seconds: 1 ));
447
- expect (findClockWithTime ('0:52' ), findsOneWidget);
532
+ expect (findClockWithTime (Side .black, '0:52' ), findsOneWidget);
448
533
});
449
534
450
535
testWidgets ('onEmergency' , (WidgetTester tester) async {
@@ -464,11 +549,11 @@ void main() {
464
549
overrides: [soundServiceProvider.overrideWith ((_) => mockSoundService)],
465
550
);
466
551
expect (
467
- tester.widget <Clock >(findClockWithTime ('0:40' )).emergencyThreshold,
552
+ tester.widget <Clock >(findClockWithTime (Side .white, '0:40' )).emergencyThreshold,
468
553
const Duration (seconds: 30 ),
469
554
);
470
555
await tester.pump (const Duration (seconds: 10 ));
471
- expect (findClockWithTime ('0:30' ), findsOneWidget);
556
+ expect (findClockWithTime (Side .white, '0:30' ), findsOneWidget);
472
557
verify (() => mockSoundService.play (Sound .lowTime)).called (1 );
473
558
});
474
559
@@ -489,19 +574,15 @@ void main() {
489
574
emerg: Duration (seconds: 30 ),
490
575
),
491
576
);
492
- expect (
493
- tester.widgetList <Clock >(find.byType (Clock )).first.active,
494
- true ,
495
- reason: 'black clock is active' ,
496
- );
577
+ expect (tester.widget <Clock >(findClock (Side .black)).active, true );
497
578
498
- expect (findClockWithTime ('2:58' ), findsOneWidget);
499
- expect (findClockWithTime ('2:54' ), findsOneWidget);
579
+ expect (findClockWithTime (Side .white, '2:58' ), findsOneWidget);
580
+ expect (findClockWithTime (Side .black, '2:54' ), findsOneWidget);
500
581
await tester.pump (const Duration (seconds: 1 ));
501
- expect (findClockWithTime ('2:53' ), findsOneWidget);
582
+ expect (findClockWithTime (Side .black, '2:53' ), findsOneWidget);
502
583
await tester.pump (const Duration (minutes: 2 , seconds: 53 ));
503
- expect (findClockWithTime ('2:58' ), findsOneWidget);
504
- expect (findClockWithTime ('0:00.0' ), findsOneWidget);
584
+ expect (findClockWithTime (Side .white, '2:58' ), findsOneWidget);
585
+ expect (findClockWithTime (Side .black, '0:00.0' ), findsOneWidget);
505
586
506
587
expect (
507
588
tester.widgetList <Clock >(find.byType (Clock )).first.active,
@@ -531,8 +612,8 @@ void main() {
531
612
2 ,
532
613
reason: 'both clocks are now inactive' ,
533
614
);
534
- expect (findClockWithTime ('2:58' ), findsOneWidget);
535
- expect (findClockWithTime ('0:00.00' ), findsOneWidget);
615
+ expect (findClockWithTime (Side .white, '2:58' ), findsOneWidget);
616
+ expect (findClockWithTime (Side .black, '0:00.00' ), findsOneWidget);
536
617
537
618
// wait for the dong
538
619
await tester.pump (const Duration (seconds: 500 ));
@@ -654,10 +735,16 @@ void main() {
654
735
});
655
736
}
656
737
657
- Finder findClockWithTime (String text, {bool skipOffstage = true }) {
738
+ /// Finds the clock on the specified [side] .
739
+ Finder findClock (Side side, {bool skipOffstage = true }) {
740
+ return find.byKey (ValueKey ('${side .name }-clock' ), skipOffstage: skipOffstage);
741
+ }
742
+
743
+ /// Finds the clock with the given [text] on the specified [side] .
744
+ Finder findClockWithTime (Side side, String text, {bool skipOffstage = true }) {
658
745
return find.ancestor (
659
746
of: find.text (text, findRichText: true , skipOffstage: skipOffstage),
660
- matching: find.byType ( Clock , skipOffstage: skipOffstage),
747
+ matching: find.byKey ( ValueKey ( '${ side . name }-clock' ) , skipOffstage: skipOffstage),
661
748
);
662
749
}
663
750
0 commit comments