From 93a4ccaee0d924d3c933715f0229d46440805505 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 12 Nov 2025 20:02:01 +0100 Subject: [PATCH] Added missing methods and implemented tests --- lib/data/dao/game_dao.dart | 18 ++- lib/data/dao/game_dao.g.dart | 1 + lib/data/dao/group_dao.dart | 15 ++- lib/data/dao/player_game_dao.g.dart | 2 +- lib/data/dao/player_group_dao.dart | 37 +++++- lib/data/db/tables/player_game_table.dart | 4 +- lib/main.dart | 30 +++++ test/db_tests/game_test.dart | 111 ++++++++++++++++++ test/db_tests/group_test.dart | 130 ++++++++++++++++++++++ test/db_tests/player_test.dart | 66 +++++++++++ test/widget_test.dart | 29 ----- 11 files changed, 402 insertions(+), 41 deletions(-) create mode 100644 test/db_tests/game_test.dart create mode 100644 test/db_tests/group_test.dart create mode 100644 test/db_tests/player_test.dart delete mode 100644 test/widget_test.dart diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 775c509..9379623 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -57,13 +57,21 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { GameTableCompanion.insert( id: game.id, name: game.name, - winnerId: Value(game.winner), + winnerId: game.winner, ), mode: InsertMode.insertOrReplace, ); }); } + /// Deletes the game with the given [gameId] from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteGame({required String gameId}) async { + final query = delete(gameTable)..where((g) => g.id.equals(gameId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + /// Retrieves the number of games in the database. Future getGameCount() async { final count = @@ -72,4 +80,12 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { .getSingle(); return count ?? 0; } + + /// Checks if a game with the given [gameId] exists in the database. + /// Returns `true` if the game exists, otherwise `false`. + Future gameExists({required String gameId}) async { + final query = select(gameTable)..where((g) => g.id.equals(gameId)); + final result = await query.getSingleOrNull(); + return result != null; + } } diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart index b5a29fe..ebf5524 100644 --- a/lib/data/dao/game_dao.g.dart +++ b/lib/data/dao/game_dao.g.dart @@ -4,5 +4,6 @@ part of 'game_dao.dart'; // ignore_for_file: type=lint mixin _$GameDaoMixin on DatabaseAccessor { + $PlayerTableTable get playerTable => attachedDatabase.playerTable; $GameTableTable get gameTable => attachedDatabase.gameTable; } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index d449326..1f0e2c8 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -51,6 +51,9 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { .toList(), ), ); + await Future.wait( + group.members.map((player) => db.playerDao.addPlayer(player: player)), + ); }); } @@ -65,11 +68,11 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { /// Updates the name of the group with the given [id] to [newName]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updateGroupname({ - required String id, + required String groupId, required String newName, }) async { final rowsAffected = - await (update(groupTable)..where((g) => g.id.equals(id))).write( + await (update(groupTable)..where((g) => g.id.equals(groupId))).write( GroupTableCompanion(name: Value(newName)), ); return rowsAffected > 0; @@ -83,4 +86,12 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { .getSingle(); return count ?? 0; } + + /// Checks if a group with the given [groupId] exists in the database. + /// Returns `true` if the group exists, `false` otherwise. + Future groupExists({required String groupId}) async { + final query = select(groupTable)..where((g) => g.id.equals(groupId)); + final result = await query.getSingleOrNull(); + return result != null; + } } diff --git a/lib/data/dao/player_game_dao.g.dart b/lib/data/dao/player_game_dao.g.dart index 0d5f5e1..4d0a192 100644 --- a/lib/data/dao/player_game_dao.g.dart +++ b/lib/data/dao/player_game_dao.g.dart @@ -5,6 +5,6 @@ part of 'player_game_dao.dart'; // ignore_for_file: type=lint mixin _$PlayerGameDaoMixin on DatabaseAccessor { $PlayerTableTable get playerTable => attachedDatabase.playerTable; - $GroupTableTable get groupTable => attachedDatabase.groupTable; + $GameTableTable get gameTable => attachedDatabase.gameTable; $PlayerGameTableTable get playerGameTable => attachedDatabase.playerGameTable; } diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 5b2a095..fe067ae 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -16,7 +16,7 @@ class PlayerGroupDao extends DatabaseAccessor ..where((pG) => pG.groupId.equals(groupId)); final result = await query.get(); - List groupMembers = []; + List groupMembers = List.empty(growable: true); for (var entry in result) { final player = await db.playerDao.getPlayerById(playerId: entry.playerId); @@ -38,13 +38,38 @@ class PlayerGroupDao extends DatabaseAccessor return rowsAffected > 0; } - /// Adds a player to a group with the given [playerId] and [groupId]. - Future addPlayerToGroup({ + /// Adds a [player] to a group with the given [groupId]. + /// If the player is already in the group, no action is taken. + /// If the player does not exist in the player table, they are added. + /// Returns `true` if the player was added, otherwise `false`. + Future addPlayerToGroup({ + required Player player, + required String groupId, + }) async { + if (await isPlayerInGroup(playerId: player.id, groupId: groupId)) { + return false; + } + + if (await db.playerDao.playerExists(playerId: player.id) == false) { + db.playerDao.addPlayer(player: player); + } + + await into(playerGroupTable).insert( + PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), + ); + + return true; + } + + /// Checks if a player with [playerId] is in the group with [groupId]. + /// Returns `true` if the player is in the group, otherwise `false`. + Future isPlayerInGroup({ required String playerId, required String groupId, }) async { - await into(playerGroupTable).insert( - PlayerGroupTableCompanion.insert(playerId: playerId, groupId: groupId), - ); + final query = select(playerGroupTable) + ..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId)); + final result = await query.getSingleOrNull(); + return result != null; } } diff --git a/lib/data/db/tables/player_game_table.dart b/lib/data/db/tables/player_game_table.dart index 79b6df2..74c36fe 100644 --- a/lib/data/db/tables/player_game_table.dart +++ b/lib/data/db/tables/player_game_table.dart @@ -1,12 +1,12 @@ import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/game_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; class PlayerGameTable extends Table { TextColumn get playerId => text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); TextColumn get gameId => - text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); + text().references(GameTable, #id, onDelete: KeyAction.cascade)(); @override Set> get primaryKey => {playerId, gameId}; diff --git a/lib/main.dart b/lib/main.dart index 98c40f8..5aa9cfc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; @@ -19,6 +22,8 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { + dbCheck(context); + return MaterialApp( debugShowCheckedModeBanner: false, title: 'Game Tracker', @@ -39,4 +44,29 @@ class GameTracker extends StatelessWidget { home: const CustomNavigationBar(), ); } + + Future dbCheck(BuildContext context) async { + Player player1 = Player(id: 'p1', name: 'Alice'); + Player player2 = Player(id: 'p2', name: 'Bob'); + Player player3 = Player(id: 'p3', name: 'Charlie'); + Player player4 = Player(id: 'p4', name: 'Diana'); + Group testgroup = Group( + id: 'gr1', + name: 'Test Group', + members: [player1, player2, player3], + ); + Game testgame = Game( + id: 'ga1', + name: 'Test Game', + winner: player1.id, + players: [player4], + group: testgroup, + ); + + final db = Provider.of(context, listen: false); + //await db.gameDao.addGame(game: testgame); + print('Game added: ${testgame.name}'); + final game = await db.gameDao.getGameById(gameId: testgame.id); + print(game.toString()); + } } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart new file mode 100644 index 0000000..fae8fc5 --- /dev/null +++ b/test/db_tests/game_test.dart @@ -0,0 +1,111 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Group testgroup; + late Game testgame; + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + testPlayer = Player(id: 'test_id', name: 'Test Player'); + player1 = Player(id: 'p1', name: 'Alice'); + player2 = Player(id: 'p2', name: 'Bob'); + player3 = Player(id: 'p3', name: 'Charlie'); + player4 = Player(id: 'p4', name: 'Diana'); + testgroup = Group( + id: 'gr1', + name: 'Test Group', + members: [player1, player2, player3], + ); + testgame = Game( + id: 'ga1', + name: 'Test Game', + group: testgroup, + players: [player4], + ); + }); + tearDown(() async { + await database.close(); + }); + + group('game tests', () { + test('game is added correctly', () async { + await database.gameDao.addGame(game: testgame); + + final result = await database.gameDao.getGameById(gameId: testgame.id); + + expect(result.id, testgame.id); + expect(result.name, testgame.name); + expect(result.winner, testgame.winner); + + if (result.group != null) { + expect(result.group!.members.length, testgroup.members.length); + + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.group!.members[i].id, testgroup.members[i].id); + expect(result.group!.members[i].name, testgroup.members[i].name); + } + } else { + fail('Group is null'); + } + if (result.players != null) { + expect(result.players!.length, testgame.players!.length); + + for (int i = 0; i < testgame.players!.length; i++) { + expect(result.players![i].id, testgame.players![i].id); + expect(result.players![i].name, testgame.players![i].name); + } + } else { + fail('Players is null'); + } + }); + + test('game is deleted correctly', () async { + await database.gameDao.addGame(game: testgame); + + final gameDeleted = await database.gameDao.deleteGame( + gameId: testgame.id, + ); + expect(gameDeleted, true); + + final gameExists = await database.gameDao.gameExists(gameId: testgame.id); + expect(gameExists, false); + }); + + test('get game count works correctly', () async { + final initialCount = await database.gameDao.getGameCount(); + expect(initialCount, 0); + + await database.gameDao.addGame(game: testgame); + + final gameAdded = await database.gameDao.getGameCount(); + expect(gameAdded, 1); + + final gameRemoved = await database.gameDao.deleteGame( + gameId: testgame.id, + ); + expect(gameRemoved, true); + + final finalCount = await database.gameDao.getGameCount(); + expect(finalCount, 0); + }); + }); +} diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart new file mode 100644 index 0000000..cdf514f --- /dev/null +++ b/test/db_tests/group_test.dart @@ -0,0 +1,130 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player player1; + late Player player2; + late Player player3; + late Player player4; + late Group testgroup; + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + player1 = Player(id: 'p1', name: 'Alice'); + player2 = Player(id: 'p2', name: 'Bob'); + player3 = Player(id: 'p3', name: 'Charlie'); + player4 = Player(id: 'p4', name: 'Diana'); + testgroup = Group( + id: 'gr1', + name: 'Test Group', + members: [player1, player2, player3], + ); + }); + tearDown(() async { + await database.close(); + }); + + test('group and group members gets added correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final result = await database.groupDao.getGroupById(groupId: testgroup.id); + + expect(result.id, testgroup.id); + expect(result.name, testgroup.name); + + expect(result.members.length, testgroup.members.length); + for (int i = 0; i < testgroup.members.length; i++) { + expect(result.members[i].id, testgroup.members[i].id); + expect(result.members[i].name, testgroup.members[i].name); + } + }); + + test('group gets deleted correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final groupDeleted = await database.groupDao.deleteGroup( + groupId: testgroup.id, + ); + expect(groupDeleted, true); + + final groupExists = await database.groupDao.groupExists( + groupId: testgroup.id, + ); + expect(groupExists, false); + }); + + test('group name gets updated correcly ', () async { + await database.groupDao.addGroup(group: testgroup); + + const newGroupName = 'new group name'; + + await database.groupDao.updateGroupname( + groupId: testgroup.id, + newName: newGroupName, + ); + + final result = await database.groupDao.getGroupById(groupId: testgroup.id); + expect(result.name, newGroupName); + }); + + test('Adding player to group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + await database.playerGroupDao.addPlayerToGroup( + player: player4, + groupId: testgroup.id, + ); + + final playerAdded = await database.playerGroupDao.isPlayerInGroup( + playerId: player4.id, + groupId: testgroup.id, + ); + + expect(playerAdded, true); + + final playerAdded2 = await database.playerGroupDao.isPlayerInGroup( + playerId: 'a', + groupId: testgroup.id, + ); + + expect(playerAdded2, false); + + expect(playerAdded, true); + + final result = await database.groupDao.getGroupById(groupId: testgroup.id); + expect(result.members.length, testgroup.members.length + 1); + + final addedPlayer = result.members.firstWhere((p) => p.id == player4.id); + expect(addedPlayer.name, player4.name); + }); + + test('Removing player from group works correctly', () async { + await database.groupDao.addGroup(group: testgroup); + + final playerToRemove = testgroup.members[0]; + + final removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: playerToRemove.id, + groupId: testgroup.id, + ); + expect(removed, true); + + final result = await database.groupDao.getGroupById(groupId: testgroup.id); + expect(result.members.length, testgroup.members.length - 1); + + final playerExists = result.members.any((p) => p.id == playerToRemove.id); + expect(playerExists, false); + }); +} diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart new file mode 100644 index 0000000..a53f2cc --- /dev/null +++ b/test/db_tests/player_test.dart @@ -0,0 +1,66 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer; + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + testPlayer = Player(id: 'test_id', name: 'Test Player'); + }); + tearDown(() async { + await database.close(); + }); + + group('player tests', () { + test('players get inserted correcly ', () async { + await database.playerDao.addPlayer(player: testPlayer); + final result = await database.playerDao.getPlayerById( + playerId: testPlayer.id, + ); + + expect(result.id, testPlayer.id); + expect(result.name, testPlayer.name); + }); + + test('players get deleted correcly ', () async { + await database.playerDao.addPlayer(player: testPlayer); + final playerDeleted = await database.playerDao.deletePlayer( + playerId: testPlayer.id, + ); + expect(playerDeleted, true); + + final playerExists = await database.playerDao.playerExists( + playerId: testPlayer.id, + ); + expect(playerExists, false); + }); + + test('player name gets updated correcly ', () async { + await database.playerDao.addPlayer(player: testPlayer); + + const newPlayerName = 'new player name'; + + await database.playerDao.updatePlayername( + playerId: testPlayer.id, + newName: newPlayerName, + ); + + final result = await database.playerDao.getPlayerById( + playerId: testPlayer.id, + ); + expect(result.name, newPlayerName); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index cb44eec..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:game_tracker/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const GameTracker()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}