From b89d3cff65b205b349ee5f3f2e85096373926b34 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:01:58 +0100 Subject: [PATCH 01/11] feat: add onTappedSquare to Chessboard widget Closes #53 --- lib/src/widgets/board.dart | 10 +++++++++ test/widgets/board_test.dart | 41 ++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index b610693..745912a 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -44,6 +44,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { required this.fen, this.opponentsPiecesUpsideDown = false, this.lastMove, + this.onTappedSquare, required this.game, this.shapes, this.annotations, @@ -60,6 +61,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { required this.orientation, required this.fen, this.lastMove, + this.onTappedSquare, this.shapes, this.annotations, }) : _size = size, @@ -88,6 +90,12 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { /// Last move played, used to highlight corresponding squares. final Move? lastMove; + /// Callback called after a square has been tapped. + /// + /// This will be called even when the board is not interactable. + /// For a callback when a move has been made, use [GameData.onMove]. + final void Function(Square)? onTappedSquare; + /// Game state of the board. /// /// If `null`, the board cannot be interacted with. @@ -549,6 +557,8 @@ class _BoardState extends State { final square = widget.offsetSquare(details.localPosition); if (square == null) return; + widget.onTappedSquare?.call(square); + final Piece? piece = pieces[square]; if (widget.settings.drawShape.enable) { diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index 35a303f..108d096 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -6,19 +6,26 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:dartchess/dartchess.dart'; import 'package:chessground/chessground.dart'; +import 'package:mocktail/mocktail.dart'; const boardSize = 200.0; const squareSize = boardSize / 8; +class OnTappedSquareMock extends Mock { + void call(Square square); +} + void main() { group('Non-interactive board', () { - const viewOnlyBoard = Chessboard.fixed( + final onTappedSquare = OnTappedSquareMock(); + final viewOnlyBoard = Chessboard.fixed( size: boardSize, orientation: Side.white, fen: kInitialFEN, - settings: ChessboardSettings( + settings: const ChessboardSettings( drawShape: DrawShapeOptions(enable: true), ), + onTappedSquare: onTappedSquare.call, ); testWidgets('initial position display', (WidgetTester tester) async { @@ -29,11 +36,16 @@ void main() { }); testWidgets('cannot select piece', (WidgetTester tester) async { + reset(onTappedSquare); + await tester.pumpWidget(viewOnlyBoard); await tester.tap(find.byKey(const Key('e2-whitepawn'))); await tester.pump(); expect(find.byKey(const Key('e2-selected')), findsNothing); + + verify(() => onTappedSquare.call(Square.e2)).called(1); + verifyNoMoreInteractions(onTappedSquare); }); testWidgets('background is constrained to the size of the board', ( @@ -113,11 +125,13 @@ void main() { border: BoardBorder(width: 16.0, color: Color(0xFF000000)), ), ]) { + final onTappedSquare = OnTappedSquareMock(); await tester.pumpWidget( _TestApp( initialPlayerSide: PlayerSide.both, settings: settings, key: ValueKey(settings.hashCode), + onTappedSquare: onTappedSquare.call, ), ); await tester.tap(find.byKey(const Key('a2-whitepawn'))); @@ -156,6 +170,17 @@ void main() { await tester.tap(find.byKey(const Key('e7-blackpawn'))); await tester.pump(); expect(find.byKey(const Key('e7-selected')), findsNothing); + + verifyInOrder([ + () => onTappedSquare.call(Square.a2), + () => onTappedSquare.call(Square.a2), + () => onTappedSquare.call(Square.a1), + () => onTappedSquare.call(Square.e7), + () => onTappedSquare.call(Square.a1), + () => onTappedSquare.call(Square.c4), + () => onTappedSquare.call(Square.e7), + ]); + verifyNoMoreInteractions(onTappedSquare); } }); @@ -205,11 +230,13 @@ void main() { border: BoardBorder(width: 16.0, color: Color(0xFF000000)), ), ]) { + final onTappedSquare = OnTappedSquareMock(); await tester.pumpWidget( _TestApp( initialPlayerSide: PlayerSide.both, settings: settings, key: ValueKey(settings.hashCode), + onTappedSquare: onTappedSquare.call, ), ); await tester.tap(find.byKey(const Key('e2-whitepawn'))); @@ -229,6 +256,12 @@ void main() { expect(find.byKey(const Key('e2-whitepawn')), findsNothing); expect(find.byKey(const Key('e2-lastMove')), findsOneWidget); expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); + + verifyInOrder([ + () => onTappedSquare.call(Square.e2), + () => onTappedSquare.call(Square.e2), + ]); + verifyNoMoreInteractions(onTappedSquare); } }); @@ -1555,6 +1588,7 @@ class _TestApp extends StatefulWidget { this.enableDrawingShapes = false, this.shouldPlayOpponentMove = false, this.gameEventStream, + this.onTappedSquare, super.key, }); @@ -1573,6 +1607,8 @@ class _TestApp extends StatefulWidget { /// A stream of game events final Stream? gameEventStream; + final void Function(Square)? onTappedSquare; + @override State<_TestApp> createState() => _TestAppState(); } @@ -1729,6 +1765,7 @@ class _TestAppState extends State<_TestApp> { }, ), ), + onTappedSquare: widget.onTappedSquare, shapes: shapes, ), ), From 393a502ca6a5378c94d33543b440223dca059e55 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:03:56 +0100 Subject: [PATCH 02/11] feat: add squareHighlights to Chessboard We'll need this if we want to switch the coordinate trainer in the app from `ChessboardEditor` to `Chessboard` in order to fix https://github.com/lichess-org/mobile/issues/1017 --- lib/src/widgets/board.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 745912a..55e4277 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -44,6 +44,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { required this.fen, this.opponentsPiecesUpsideDown = false, this.lastMove, + this.squareHighlights = const IMap.empty(), this.onTappedSquare, required this.game, this.shapes, @@ -61,6 +62,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { required this.orientation, required this.fen, this.lastMove, + this.squareHighlights = const IMap.empty(), this.onTappedSquare, this.shapes, this.annotations, @@ -84,6 +86,8 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { /// If `true` the opponent`s pieces are displayed rotated by 180 degrees. final bool opponentsPiecesUpsideDown; + final IMap squareHighlights; + /// FEN string describing the position of the board. final String fen; @@ -275,6 +279,15 @@ class _BoardState extends State { square: checkSquare, child: CheckHighlight(size: widget.squareSize), ), + for (final MapEntry(key: square, value: highlight) + in widget.squareHighlights.entries) + PositionedSquare( + key: ValueKey('${square.name}-highlight'), + size: widget.size, + orientation: widget.orientation, + square: square, + child: highlight, + ), ]; final List objects = [ From 21c954f46dd281eace4858994a824c711d126559 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:05:45 +0100 Subject: [PATCH 03/11] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 593ed5f..a1b4827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 6.3.0 + +- Added a `onTappedSquare` callback to `Chessboard` +- `Chessboard` now supports highlighting arbitrary squares. + ## 6.2.3 - Fix board editor color filter From 42816b847c5f7424dccf9b27f7b30f979369045c Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:28:14 +0100 Subject: [PATCH 04/11] use IMapConst --- lib/src/widgets/board.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 55e4277..4510795 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -44,7 +44,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { required this.fen, this.opponentsPiecesUpsideDown = false, this.lastMove, - this.squareHighlights = const IMap.empty(), + this.squareHighlights = const IMapConst({}), this.onTappedSquare, required this.game, this.shapes, @@ -62,7 +62,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { required this.orientation, required this.fen, this.lastMove, - this.squareHighlights = const IMap.empty(), + this.squareHighlights = const IMapConst({}), this.onTappedSquare, this.shapes, this.annotations, From 5c95a9978206c8563c295abb6e263695d553ee1e Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:30:25 +0100 Subject: [PATCH 05/11] use tearDown to reset mock --- test/widgets/board_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index 108d096..b7b70b1 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -18,6 +18,10 @@ class OnTappedSquareMock extends Mock { void main() { group('Non-interactive board', () { final onTappedSquare = OnTappedSquareMock(); + tearDown(() { + reset(onTappedSquare); + }); + final viewOnlyBoard = Chessboard.fixed( size: boardSize, orientation: Side.white, @@ -36,8 +40,6 @@ void main() { }); testWidgets('cannot select piece', (WidgetTester tester) async { - reset(onTappedSquare); - await tester.pumpWidget(viewOnlyBoard); await tester.tap(find.byKey(const Key('e2-whitepawn'))); await tester.pump(); From f2f4773a314808e678009b213cb3d56f033dbf11 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Wed, 8 Jan 2025 19:53:59 +0100 Subject: [PATCH 06/11] move callback to onPointerUp instead of onPointerDown --- lib/src/widgets/board.dart | 14 +++++++++----- test/widgets/board_test.dart | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 4510795..9d35aba 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -378,7 +378,9 @@ class _BoardState extends State { final board = Listener( onPointerDown: enableListeners ? _onPointerDown : null, onPointerMove: enableListeners ? _onPointerMove : null, - onPointerUp: enableListeners ? _onPointerUp : null, + // This one has to enabled even if the board is non interactive, + // since it triggers the onTappedSquare callback. + onPointerUp: _onPointerUp, onPointerCancel: enableListeners ? _onPointerCancel : null, child: SizedBox.square( key: const ValueKey('board-container'), @@ -570,8 +572,6 @@ class _BoardState extends State { final square = widget.offsetSquare(details.localPosition); if (square == null) return; - widget.onTappedSquare?.call(square); - final Piece? piece = pieces[square]; if (widget.settings.drawShape.enable) { @@ -752,13 +752,17 @@ class _BoardState extends State { return; } + final square = widget.offsetSquare(details.localPosition); + + if (square != null) { + widget.onTappedSquare?.call(square); + } + if (_currentPointerDownEvent == null || _currentPointerDownEvent!.pointer != details.pointer) { return; } - final square = widget.offsetSquare(details.localPosition); - // handle pointer up while dragging a piece if (_dragAvatar != null) { bool shouldDeselect = true; diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index b7b70b1..4b78277 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -261,7 +261,7 @@ void main() { verifyInOrder([ () => onTappedSquare.call(Square.e2), - () => onTappedSquare.call(Square.e2), + () => onTappedSquare.call(Square.e4), ]); verifyNoMoreInteractions(onTappedSquare); } From 3a259f203277aded21449a382e582fbf49103ef2 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:43:31 +0100 Subject: [PATCH 07/11] enable listeners always, only call onTappedSquare if pointer down and up on same square --- lib/src/widgets/board.dart | 18 ++++++++++++------ test/widgets/board_test.dart | 1 - 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 9d35aba..248fbe0 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -170,6 +170,11 @@ class _BoardState extends State { /// gesture. PointerDownEvent? _currentPointerDownEvent; + /// Square that was hit as part of [_currentPointerDownEvent]. + /// + /// This field is reset to null when the pointer is released (up or cancel). + Square? _currentPointerDownSquare; + /// Current render box during drag. // ignore: use_late_for_private_fields_and_variables RenderBox? _renderBox; @@ -373,15 +378,13 @@ class _BoardState extends State { ), ]; - final enableListeners = widget.interactive || settings.drawShape.enable; - final board = Listener( - onPointerDown: enableListeners ? _onPointerDown : null, - onPointerMove: enableListeners ? _onPointerMove : null, + onPointerDown: _onPointerDown, + onPointerMove: _onPointerMove, // This one has to enabled even if the board is non interactive, // since it triggers the onTappedSquare callback. onPointerUp: _onPointerUp, - onPointerCancel: enableListeners ? _onPointerCancel : null, + onPointerCancel: _onPointerCancel, child: SizedBox.square( key: const ValueKey('board-container'), dimension: widget.size, @@ -466,6 +469,7 @@ class _BoardState extends State { } if (widget.interactive == false) { _currentPointerDownEvent = null; + _currentPointerDownSquare = null; _dragAvatar?.cancel(); _dragAvatar = null; _draggedPieceSquare = null; @@ -571,6 +575,7 @@ class _BoardState extends State { final square = widget.offsetSquare(details.localPosition); if (square == null) return; + _currentPointerDownSquare = square; final Piece? piece = pieces[square]; @@ -754,8 +759,9 @@ class _BoardState extends State { final square = widget.offsetSquare(details.localPosition); - if (square != null) { + if (square != null && square == _currentPointerDownSquare) { widget.onTappedSquare?.call(square); + _currentPointerDownSquare = null; } if (_currentPointerDownEvent == null || diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index 4b78277..3fda122 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -261,7 +261,6 @@ void main() { verifyInOrder([ () => onTappedSquare.call(Square.e2), - () => onTappedSquare.call(Square.e4), ]); verifyNoMoreInteractions(onTappedSquare); } From a3d6712db91b2a9c02a053ee96b31a643b710d91 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:56:01 +0100 Subject: [PATCH 08/11] add dedicated test, remove obsolete comment --- lib/src/widgets/board.dart | 2 -- test/widgets/board_test.dart | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index 248fbe0..da94a4f 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -381,8 +381,6 @@ class _BoardState extends State { final board = Listener( onPointerDown: _onPointerDown, onPointerMove: _onPointerMove, - // This one has to enabled even if the board is non interactive, - // since it triggers the onTappedSquare callback. onPointerUp: _onPointerUp, onPointerCancel: _onPointerCancel, child: SizedBox.square( diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index 3fda122..60489da 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -632,6 +632,61 @@ void main() { }); }); + testWidgets('onTappedSquare callback', (WidgetTester tester) async { + final controller = StreamController.broadcast(); + + addTearDown(() { + controller.close(); + }); + + final onTappedSquare = OnTappedSquareMock(); + await tester.pumpWidget( + _TestApp( + initialPlayerSide: PlayerSide.white, + gameEventStream: controller.stream, + onTappedSquare: onTappedSquare.call, + ), + ); + + // Trigger callback by tapping a square with a piece on it + await tester.tapAt(squareOffset(tester, Square.a1)); + + // Trigger callback by tapping an empty square + await tester.tapAt(squareOffset(tester, Square.e4)); + + // Drag a piece to the same square -> should trigger callback + await tester.dragFrom( + squareOffset(tester, Square.a2), + const Offset(0, -(squareSize / 2)), + ); + + // Drag from a empty square to the same square -> should trigger callback + await tester.dragFrom( + squareOffset(tester, Square.a4), + const Offset(0, -(squareSize / 2)), + ); + + // Drag piece to a different square (i.e. make a move) -> should not trigger callback + await tester.dragFrom( + squareOffset(tester, Square.a2), + const Offset(0, -squareSize), + ); + + // Callback should be triggered even if the board is non-interactive + controller.add(GameEvent.nonInteractiveBoardEvent); + await tester.pump(const Duration(milliseconds: 1)); + await tester.tapAt(squareOffset(tester, Square.e3)); + + verifyInOrder([ + () => onTappedSquare(Square.a1), + () => onTappedSquare(Square.e4), + () => onTappedSquare(Square.a2), + () => onTappedSquare(Square.a4), + () => onTappedSquare(Square.e3), + ]); + verifyNoMoreInteractions(onTappedSquare); + }); + group('Promotion', () { testWidgets('can display the selector', (WidgetTester tester) async { await tester.pumpWidget( From ac57be5cfdf2d6b63d1e8bf78c4577b9e1b2e338 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:57:20 +0100 Subject: [PATCH 09/11] extend test --- test/widgets/board_test.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index 60489da..7e89b7b 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -666,6 +666,12 @@ void main() { const Offset(0, -(squareSize / 2)), ); + // Drag from an empty square another empty square -> should not trigger callback + await tester.dragFrom( + squareOffset(tester, Square.a4), + const Offset(0, -squareSize), + ); + // Drag piece to a different square (i.e. make a move) -> should not trigger callback await tester.dragFrom( squareOffset(tester, Square.a2), From 1a0e25df4aa86abc52dc395b548333535872a3d2 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Sun, 12 Jan 2025 11:58:59 +0800 Subject: [PATCH 10/11] Switch to pointer down event --- lib/src/widgets/board.dart | 31 ++++++---------- test/widgets/board_test.dart | 69 +++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/lib/src/widgets/board.dart b/lib/src/widgets/board.dart index da94a4f..fc461e7 100644 --- a/lib/src/widgets/board.dart +++ b/lib/src/widgets/board.dart @@ -45,7 +45,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { this.opponentsPiecesUpsideDown = false, this.lastMove, this.squareHighlights = const IMapConst({}), - this.onTappedSquare, + this.onTouchedSquare, required this.game, this.shapes, this.annotations, @@ -63,7 +63,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { required this.fen, this.lastMove, this.squareHighlights = const IMapConst({}), - this.onTappedSquare, + this.onTouchedSquare, this.shapes, this.annotations, }) : _size = size, @@ -86,6 +86,7 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { /// If `true` the opponent`s pieces are displayed rotated by 180 degrees. final bool opponentsPiecesUpsideDown; + /// Squares to highlight on the board. final IMap squareHighlights; /// FEN string describing the position of the board. @@ -94,11 +95,11 @@ class Chessboard extends StatefulWidget with ChessboardGeometry { /// Last move played, used to highlight corresponding squares. final Move? lastMove; - /// Callback called after a square has been tapped. + /// Callback called after a square has been touched. /// - /// This will be called even when the board is not interactable. - /// For a callback when a move has been made, use [GameData.onMove]. - final void Function(Square)? onTappedSquare; + /// This will be called even when the board is not interactable, with each [PointerDownEvent] that + /// targets a square. + final void Function(Square)? onTouchedSquare; /// Game state of the board. /// @@ -170,11 +171,6 @@ class _BoardState extends State { /// gesture. PointerDownEvent? _currentPointerDownEvent; - /// Square that was hit as part of [_currentPointerDownEvent]. - /// - /// This field is reset to null when the pointer is released (up or cancel). - Square? _currentPointerDownSquare; - /// Current render box during drag. // ignore: use_late_for_private_fields_and_variables RenderBox? _renderBox; @@ -467,7 +463,6 @@ class _BoardState extends State { } if (widget.interactive == false) { _currentPointerDownEvent = null; - _currentPointerDownSquare = null; _dragAvatar?.cancel(); _dragAvatar = null; _draggedPieceSquare = null; @@ -573,7 +568,8 @@ class _BoardState extends State { final square = widget.offsetSquare(details.localPosition); if (square == null) return; - _currentPointerDownSquare = square; + + widget.onTouchedSquare?.call(square); final Piece? piece = pieces[square]; @@ -755,18 +751,13 @@ class _BoardState extends State { return; } - final square = widget.offsetSquare(details.localPosition); - - if (square != null && square == _currentPointerDownSquare) { - widget.onTappedSquare?.call(square); - _currentPointerDownSquare = null; - } - if (_currentPointerDownEvent == null || _currentPointerDownEvent!.pointer != details.pointer) { return; } + final square = widget.offsetSquare(details.localPosition); + // handle pointer up while dragging a piece if (_dragAvatar != null) { bool shouldDeselect = true; diff --git a/test/widgets/board_test.dart b/test/widgets/board_test.dart index 7e89b7b..c796840 100644 --- a/test/widgets/board_test.dart +++ b/test/widgets/board_test.dart @@ -17,9 +17,9 @@ class OnTappedSquareMock extends Mock { void main() { group('Non-interactive board', () { - final onTappedSquare = OnTappedSquareMock(); + final onTouchedSquare = OnTappedSquareMock(); tearDown(() { - reset(onTappedSquare); + reset(onTouchedSquare); }); final viewOnlyBoard = Chessboard.fixed( @@ -29,7 +29,7 @@ void main() { settings: const ChessboardSettings( drawShape: DrawShapeOptions(enable: true), ), - onTappedSquare: onTappedSquare.call, + onTouchedSquare: onTouchedSquare.call, ); testWidgets('initial position display', (WidgetTester tester) async { @@ -46,8 +46,8 @@ void main() { expect(find.byKey(const Key('e2-selected')), findsNothing); - verify(() => onTappedSquare.call(Square.e2)).called(1); - verifyNoMoreInteractions(onTappedSquare); + verify(() => onTouchedSquare.call(Square.e2)).called(1); + verifyNoMoreInteractions(onTouchedSquare); }); testWidgets('background is constrained to the size of the board', ( @@ -127,13 +127,13 @@ void main() { border: BoardBorder(width: 16.0, color: Color(0xFF000000)), ), ]) { - final onTappedSquare = OnTappedSquareMock(); + final onTouchedSquare = OnTappedSquareMock(); await tester.pumpWidget( _TestApp( initialPlayerSide: PlayerSide.both, settings: settings, key: ValueKey(settings.hashCode), - onTappedSquare: onTappedSquare.call, + onTouchedSquare: onTouchedSquare.call, ), ); await tester.tap(find.byKey(const Key('a2-whitepawn'))); @@ -174,15 +174,15 @@ void main() { expect(find.byKey(const Key('e7-selected')), findsNothing); verifyInOrder([ - () => onTappedSquare.call(Square.a2), - () => onTappedSquare.call(Square.a2), - () => onTappedSquare.call(Square.a1), - () => onTappedSquare.call(Square.e7), - () => onTappedSquare.call(Square.a1), - () => onTappedSquare.call(Square.c4), - () => onTappedSquare.call(Square.e7), + () => onTouchedSquare.call(Square.a2), + () => onTouchedSquare.call(Square.a2), + () => onTouchedSquare.call(Square.a1), + () => onTouchedSquare.call(Square.e7), + () => onTouchedSquare.call(Square.a1), + () => onTouchedSquare.call(Square.c4), + () => onTouchedSquare.call(Square.e7), ]); - verifyNoMoreInteractions(onTappedSquare); + verifyNoMoreInteractions(onTouchedSquare); } }); @@ -232,13 +232,13 @@ void main() { border: BoardBorder(width: 16.0, color: Color(0xFF000000)), ), ]) { - final onTappedSquare = OnTappedSquareMock(); + final onTouchedSquare = OnTappedSquareMock(); await tester.pumpWidget( _TestApp( initialPlayerSide: PlayerSide.both, settings: settings, key: ValueKey(settings.hashCode), - onTappedSquare: onTappedSquare.call, + onTouchedSquare: onTouchedSquare.call, ), ); await tester.tap(find.byKey(const Key('e2-whitepawn'))); @@ -260,9 +260,10 @@ void main() { expect(find.byKey(const Key('e4-lastMove')), findsOneWidget); verifyInOrder([ - () => onTappedSquare.call(Square.e2), + () => onTouchedSquare.call(Square.e2), + () => onTouchedSquare.call(Square.e2), ]); - verifyNoMoreInteractions(onTappedSquare); + verifyNoMoreInteractions(onTouchedSquare); } }); @@ -632,19 +633,19 @@ void main() { }); }); - testWidgets('onTappedSquare callback', (WidgetTester tester) async { + testWidgets('onTouchedSquare callback', (WidgetTester tester) async { final controller = StreamController.broadcast(); addTearDown(() { controller.close(); }); - final onTappedSquare = OnTappedSquareMock(); + final onTouchedSquare = OnTappedSquareMock(); await tester.pumpWidget( _TestApp( initialPlayerSide: PlayerSide.white, gameEventStream: controller.stream, - onTappedSquare: onTappedSquare.call, + onTouchedSquare: onTouchedSquare.call, ), ); @@ -666,13 +667,13 @@ void main() { const Offset(0, -(squareSize / 2)), ); - // Drag from an empty square another empty square -> should not trigger callback + // Drag from an empty square another empty square -> should trigger callback on 1st square await tester.dragFrom( squareOffset(tester, Square.a4), const Offset(0, -squareSize), ); - // Drag piece to a different square (i.e. make a move) -> should not trigger callback + // Drag piece to a different square (i.e. make a move) -> should trigger callback on 1st square await tester.dragFrom( squareOffset(tester, Square.a2), const Offset(0, -squareSize), @@ -684,13 +685,15 @@ void main() { await tester.tapAt(squareOffset(tester, Square.e3)); verifyInOrder([ - () => onTappedSquare(Square.a1), - () => onTappedSquare(Square.e4), - () => onTappedSquare(Square.a2), - () => onTappedSquare(Square.a4), - () => onTappedSquare(Square.e3), + () => onTouchedSquare(Square.a1), + () => onTouchedSquare(Square.e4), + () => onTouchedSquare(Square.a2), + () => onTouchedSquare(Square.a4), + () => onTouchedSquare(Square.a4), + () => onTouchedSquare(Square.a2), + () => onTouchedSquare(Square.e3), ]); - verifyNoMoreInteractions(onTappedSquare); + verifyNoMoreInteractions(onTouchedSquare); }); group('Promotion', () { @@ -1650,7 +1653,7 @@ class _TestApp extends StatefulWidget { this.enableDrawingShapes = false, this.shouldPlayOpponentMove = false, this.gameEventStream, - this.onTappedSquare, + this.onTouchedSquare, super.key, }); @@ -1669,7 +1672,7 @@ class _TestApp extends StatefulWidget { /// A stream of game events final Stream? gameEventStream; - final void Function(Square)? onTappedSquare; + final void Function(Square)? onTouchedSquare; @override State<_TestApp> createState() => _TestAppState(); @@ -1827,7 +1830,7 @@ class _TestAppState extends State<_TestApp> { }, ), ), - onTappedSquare: widget.onTappedSquare, + onTouchedSquare: widget.onTouchedSquare, shapes: shapes, ), ), From 47bec02dd888113a696aac4b2168151653558838 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Sun, 12 Jan 2025 15:18:22 +0800 Subject: [PATCH 11/11] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1b4827..f63c1dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 6.3.0 -- Added a `onTappedSquare` callback to `Chessboard` +- Added an `onTouchedSquare` callback to `Chessboard` - `Chessboard` now supports highlighting arbitrary squares. ## 6.2.3