5 Commits

Author SHA1 Message Date
dc0e536221 Implemented updateGameName() and tests for it
All checks were successful
Pull Request Pipeline / lint (pull_request) Successful in 2m11s
Pull Request Pipeline / test (pull_request) Successful in 3m30s
2025-11-26 14:44:41 +01:00
2a34243e69 Renamed methods for better distinction 2025-11-26 14:42:17 +01:00
499415e0c5 Added updatePlayersFromGame(), added docs & tests 2025-11-26 14:39:38 +01:00
397c5c1550 Added updateGroupOfGame(), added docc & tests 2025-11-26 14:17:11 +01:00
738f242eee Implemented methods and test for winner 2025-11-26 13:48:53 +01:00
11 changed files with 341 additions and 30 deletions

View File

@@ -65,6 +65,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
/// Adds a new [Game] to the database. /// Adds a new [Game] to the database.
/// Also adds associated players and group if they exist. /// Also adds associated players and group if they exist.
/// If a game, player, or group already exists, it will be replaced.
Future<void> addGame({required Game game}) async { Future<void> addGame({required Game game}) async {
await db.transaction(() async { await db.transaction(() async {
await into(gameTable).insert( await into(gameTable).insert(
@@ -78,7 +79,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
); );
if (game.players != null) { if (game.players != null) {
await db.playerDao.addPlayers(players: game.players!); await db.playerDao.addPlayersAsList(players: game.players!);
for (final p in game.players ?? []) { for (final p in game.players ?? []) {
await db.playerGameDao.addPlayerToGame( await db.playerGameDao.addPlayerToGame(
gameId: game.id, gameId: game.id,
@@ -89,12 +90,19 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
if (game.group != null) { if (game.group != null) {
await db.groupDao.addGroup(group: game.group!); await db.groupDao.addGroup(group: game.group!);
await db.groupGameDao.addGroupToGame(game.id, game.group!.id); await db.groupGameDao.addGroupToGame(
gameId: game.id,
groupId: game.group!.id,
);
} }
}); });
} }
Future<void> addGames({required List<Game> games}) async { /// Adds multiple [Game]s to the database in a batch operation.
/// Also adds associated players and groups if they exist.
/// If the [games] list is empty, the method returns immediately.
/// If a game, player, or group already exists, it will be replaced.
Future<void> addGamesAsList({required List<Game> games}) async {
if (games.isEmpty) return; if (games.isEmpty) return;
await db.transaction(() async { await db.transaction(() async {
// Add all games in batch // Add all games in batch
@@ -253,4 +261,62 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
final rowsAffected = await query.go(); final rowsAffected = await query.go();
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Sets the winner of the game with the given [gameId] to the player with
/// the given [winnerId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> setWinner({
required String gameId,
required String winnerId,
}) async {
final query = update(gameTable)..where((g) => g.id.equals(gameId));
final rowsAffected = await query.write(
GameTableCompanion(winnerId: Value(winnerId)),
);
return rowsAffected > 0;
}
/// Retrieves the winner of the game with the given [gameId].
/// Returns the [Player] who won the game, or `null` if no winner is set.
Future<Player?> getWinner({required String gameId}) async {
final query = select(gameTable)..where((g) => g.id.equals(gameId));
final result = await query.getSingleOrNull();
if (result == null || result.winnerId == null) {
return null;
}
final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!);
return winner;
}
/// Removes the winner of the game with the given [gameId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> removeWinner({required String gameId}) async {
final query = update(gameTable)..where((g) => g.id.equals(gameId));
final rowsAffected = await query.write(
const GameTableCompanion(winnerId: Value(null)),
);
return rowsAffected > 0;
}
/// Checks if the game with the given [gameId] has a winner set.
/// Returns `true` if a winner is set, otherwise `false`.
Future<bool> hasWinner({required String gameId}) async {
final query = select(gameTable)
..where((g) => g.id.equals(gameId) & g.winnerId.isNotNull());
final result = await query.getSingleOrNull();
return result != null;
}
/// Changes the title of the game with the given [gameId] to [newName].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateGameName({
required String gameId,
required String newName,
}) async {
final query = update(gameTable)..where((g) => g.id.equals(gameId));
final rowsAffected = await query.write(
GameTableCompanion(name: Value(newName)),
);
return rowsAffected > 0;
}
} }

View File

@@ -84,7 +84,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
/// Adds multiple groups to the database. /// Adds multiple groups to the database.
/// Also adds the group's members to the [PlayerGroupTable]. /// Also adds the group's members to the [PlayerGroupTable].
Future<void> addGroups({required List<Group> groups}) async { Future<void> addGroupsAsList({required List<Group> groups}) async {
if (groups.isEmpty) return; if (groups.isEmpty) return;
await db.transaction(() async { await db.transaction(() async {
// Deduplicate groups by id - keep first occurrence // Deduplicate groups by id - keep first occurrence

View File

@@ -11,8 +11,12 @@ class GroupGameDao extends DatabaseAccessor<AppDatabase>
GroupGameDao(super.db); GroupGameDao(super.db);
/// Associates a group with a game by inserting a record into the /// Associates a group with a game by inserting a record into the
/// [GroupGameTable]. /// [GroupGameTable]. If there is already group associated to the game,
Future<void> addGroupToGame(String gameId, String groupId) async { /// it will be replaced.
Future<void> addGroupToGame({
required String gameId,
required String groupId,
}) async {
await into(groupGameTable).insert( await into(groupGameTable).insert(
GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId),
mode: InsertMode.insertOrReplace, mode: InsertMode.insertOrReplace,
@@ -76,4 +80,17 @@ class GroupGameDao extends DatabaseAccessor<AppDatabase>
final rowsAffected = await query.go(); final rowsAffected = await query.go();
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Updates the group associated with a game to [newGroupId] based on
/// [gameId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateGroupOfGame({
required String gameId,
required String newGroupId,
}) async {
final updatedRows =
await (update(groupGameTable)..where((g) => g.gameId.equals(gameId)))
.write(GroupGameTableCompanion(groupId: Value(newGroupId)));
return updatedRows > 0;
}
} }

View File

@@ -50,7 +50,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
} }
/// Adds multiple [players] to the database in a batch operation. /// Adds multiple [players] to the database in a batch operation.
Future<bool> addPlayers({required List<Player> players}) async { Future<bool> addPlayersAsList({required List<Player> players}) async {
if (players.isEmpty) return false; if (players.isEmpty) return false;
await db.batch( await db.batch(

View File

@@ -79,4 +79,50 @@ class PlayerGameDao extends DatabaseAccessor<AppDatabase>
final rowsAffected = await query.go(); final rowsAffected = await query.go();
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Updates the players associated with a game based on the provided
/// [newPlayer] list. It adds new players and removes players that are no
/// longer associated with the game.
Future<void> updatePlayersFromGame({
required String gameId,
required List<Player> newPlayer,
}) async {
final currentPlayers = await getPlayersOfGame(gameId: gameId);
// Create sets of player IDs for easy comparison
final currentPlayerIds = currentPlayers?.map((p) => p.id).toSet() ?? {};
final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet();
// Determine players to add and remove
final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds);
final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet);
db.transaction(() async {
// Remove old players
if (playersToRemove.isNotEmpty) {
await (delete(playerGameTable)..where(
(pg) =>
pg.gameId.equals(gameId) &
pg.playerId.isIn(playersToRemove.toList()),
))
.go();
}
// Add new players
if (playersToAdd.isNotEmpty) {
final inserts = playersToAdd
.map(
(id) =>
PlayerGameTableCompanion.insert(playerId: id, gameId: gameId),
)
.toList();
await Future.wait(
inserts.map(
(c) => into(
playerGameTable,
).insert(c, mode: InsertMode.insertOrReplace),
),
);
}
});
}
} }

View File

@@ -110,9 +110,9 @@ class DataTransferService {
.toList() ?? .toList() ??
[]; [];
await db.playerDao.addPlayers(players: importedPlayers); await db.playerDao.addPlayersAsList(players: importedPlayers);
await db.groupDao.addGroups(groups: importedGroups); await db.groupDao.addGroupsAsList(groups: importedGroups);
await db.gameDao.addGames(games: importedGames); await db.gameDao.addGamesAsList(games: importedGames);
} else { } else {
return ImportResult.invalidSchema; return ImportResult.invalidSchema;
} }

View File

@@ -112,7 +112,7 @@ void main() {
}); });
test('Adding and fetching multiple games works correctly', () async { test('Adding and fetching multiple games works correctly', () async {
await database.gameDao.addGames( await database.gameDao.addGamesAsList(
games: [testGame1, testGame2, testGameOnlyGroup, testGameOnlyPlayers], games: [testGame1, testGame2, testGameOnlyGroup, testGameOnlyPlayers],
); );
@@ -234,5 +234,97 @@ void main() {
gameCount = await database.gameDao.getGameCount(); gameCount = await database.gameDao.getGameCount();
expect(gameCount, 0); expect(gameCount, 0);
}); });
test('Checking if game has winner works correclty', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.addGame(game: testGameOnlyGroup);
var hasWinner = await database.gameDao.hasWinner(gameId: testGame1.id);
expect(hasWinner, true);
hasWinner = await database.gameDao.hasWinner(
gameId: testGameOnlyGroup.id,
);
expect(hasWinner, false);
});
test('Fetching the winner of a game works correctly', () async {
await database.gameDao.addGame(game: testGame1);
final winner = await database.gameDao.getWinner(gameId: testGame1.id);
if (winner == null) {
fail('Winner is null');
} else {
expect(winner.id, testGame1.winner!.id);
expect(winner.name, testGame1.winner!.name);
expect(winner.createdAt, testGame1.winner!.createdAt);
}
});
test('Updating the winner of a game works correctly', () async {
await database.gameDao.addGame(game: testGame1);
final winner = await database.gameDao.getWinner(gameId: testGame1.id);
if (winner == null) {
fail('Winner is null');
} else {
expect(winner.id, testGame1.winner!.id);
expect(winner.name, testGame1.winner!.name);
expect(winner.createdAt, testGame1.winner!.createdAt);
expect(winner.id, testPlayer4.id);
expect(winner.id != testPlayer5.id, true);
}
await database.gameDao.setWinner(
gameId: testGame1.id,
winnerId: testPlayer5.id,
);
final newWinner = await database.gameDao.getWinner(gameId: testGame1.id);
if (newWinner == null) {
fail('New winner is null');
} else {
expect(newWinner.id, testPlayer5.id);
expect(newWinner.name, testPlayer5.name);
expect(newWinner.createdAt, testPlayer5.createdAt);
}
});
test('Removing a winner works correctly', () async {
await database.gameDao.addGame(game: testGame2);
var hasWinner = await database.gameDao.hasWinner(gameId: testGame2.id);
expect(hasWinner, true);
await database.gameDao.removeWinner(gameId: testGame2.id);
hasWinner = await database.gameDao.hasWinner(gameId: testGame2.id);
expect(hasWinner, false);
final removedWinner = await database.gameDao.getWinner(
gameId: testGame2.id,
);
expect(removedWinner, null);
});
test('Renaming a game works correctly', () async {
await database.gameDao.addGame(game: testGame1);
var fetchedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(fetchedGame.name, testGame1.name);
const newName = 'Updated Game Name';
await database.gameDao.updateGameName(
gameId: testGame1.id,
newName: newName,
);
fetchedGame = await database.gameDao.getGameById(gameId: testGame1.id);
expect(fetchedGame.name, newName);
});
}); });
} }

View File

@@ -14,7 +14,8 @@ void main() {
late Player testPlayer3; late Player testPlayer3;
late Player testPlayer4; late Player testPlayer4;
late Player testPlayer5; late Player testPlayer5;
late Group testgroup; late Group testGroup1;
late Group testGroup2;
late Game testgameWithGroup; late Game testgameWithGroup;
late Game testgameWithPlayers; late Game testgameWithPlayers;
final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fixedDate = DateTime(2025, 19, 11, 00, 11, 23);
@@ -35,15 +36,19 @@ void main() {
testPlayer3 = Player(name: 'Charlie'); testPlayer3 = Player(name: 'Charlie');
testPlayer4 = Player(name: 'Diana'); testPlayer4 = Player(name: 'Diana');
testPlayer5 = Player(name: 'Eve'); testPlayer5 = Player(name: 'Eve');
testgroup = Group( testGroup1 = Group(
name: 'Test Group', name: 'Test Group',
members: [testPlayer1, testPlayer2, testPlayer3], members: [testPlayer1, testPlayer2, testPlayer3],
); );
testGroup2 = Group(
name: 'Test Group',
members: [testPlayer3, testPlayer2],
);
testgameWithPlayers = Game( testgameWithPlayers = Game(
name: 'Test Game with Players', name: 'Test Game with Players',
players: [testPlayer4, testPlayer5], players: [testPlayer4, testPlayer5],
); );
testgameWithGroup = Game(name: 'Test Game with Group', group: testgroup); testgameWithGroup = Game(name: 'Test Game with Group', group: testGroup1);
}); });
}); });
tearDown(() async { tearDown(() async {
@@ -52,7 +57,7 @@ void main() {
group('Group-Game Tests', () { group('Group-Game Tests', () {
test('Game has group works correctly', () async { test('Game has group works correctly', () async {
await database.gameDao.addGame(game: testgameWithPlayers); await database.gameDao.addGame(game: testgameWithPlayers);
await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testGroup1);
var gameHasGroup = await database.groupGameDao.gameHasGroup( var gameHasGroup = await database.groupGameDao.gameHasGroup(
gameId: testgameWithPlayers.id, gameId: testgameWithPlayers.id,
@@ -61,8 +66,8 @@ void main() {
expect(gameHasGroup, false); expect(gameHasGroup, false);
await database.groupGameDao.addGroupToGame( await database.groupGameDao.addGroupToGame(
testgameWithPlayers.id, gameId: testgameWithPlayers.id,
testgroup.id, groupId: testGroup1.id,
); );
gameHasGroup = await database.groupGameDao.gameHasGroup( gameHasGroup = await database.groupGameDao.gameHasGroup(
@@ -74,15 +79,15 @@ void main() {
test('Adding a group to a game works correctly', () async { test('Adding a group to a game works correctly', () async {
await database.gameDao.addGame(game: testgameWithPlayers); await database.gameDao.addGame(game: testgameWithPlayers);
await database.groupDao.addGroup(group: testgroup); await database.groupDao.addGroup(group: testGroup1);
await database.groupGameDao.addGroupToGame( await database.groupGameDao.addGroupToGame(
testgameWithPlayers.id, gameId: testgameWithPlayers.id,
testgroup.id, groupId: testGroup1.id,
); );
var groupAdded = await database.groupGameDao.isGroupInGame( var groupAdded = await database.groupGameDao.isGroupInGame(
gameId: testgameWithPlayers.id, gameId: testgameWithPlayers.id,
groupId: testgroup.id, groupId: testGroup1.id,
); );
expect(groupAdded, true); expect(groupAdded, true);
@@ -120,14 +125,55 @@ void main() {
fail('Group should not be null'); fail('Group should not be null');
} }
expect(group.id, testgroup.id); expect(group.id, testGroup1.id);
expect(group.name, testgroup.name); expect(group.name, testGroup1.name);
expect(group.createdAt, testgroup.createdAt); expect(group.createdAt, testGroup1.createdAt);
expect(group.members.length, testgroup.members.length); expect(group.members.length, testGroup1.members.length);
for (int i = 0; i < group.members.length; i++) { for (int i = 0; i < group.members.length; i++) {
expect(group.members[i].id, testgroup.members[i].id); expect(group.members[i].id, testGroup1.members[i].id);
expect(group.members[i].name, testgroup.members[i].name); expect(group.members[i].name, testGroup1.members[i].name);
expect(group.members[i].createdAt, testgroup.members[i].createdAt); expect(group.members[i].createdAt, testGroup1.members[i].createdAt);
}
});
test('Updating the group of a game works correctly', () async {
await database.gameDao.addGame(game: testgameWithGroup);
var group = await database.groupGameDao.getGroupOfGame(
gameId: testgameWithGroup.id,
);
if (group == null) {
fail('Initial group should not be null');
} else {
expect(group.id, testGroup1.id);
expect(group.name, testGroup1.name);
expect(group.createdAt, testGroup1.createdAt);
expect(group.members.length, testGroup1.members.length);
}
await database.groupDao.addGroup(group: testGroup2);
await database.groupGameDao.updateGroupOfGame(
gameId: testgameWithGroup.id,
newGroupId: testGroup2.id,
);
group = await database.groupGameDao.getGroupOfGame(
gameId: testgameWithGroup.id,
);
if (group == null) {
fail('Updated group should not be null');
} else {
expect(group.id, testGroup2.id);
expect(group.name, testGroup2.name);
expect(group.createdAt, testGroup2.createdAt);
expect(group.members.length, testGroup2.members.length);
for (int i = 0; i < group.members.length; i++) {
expect(group.members[i].id, testGroup2.members[i].id);
expect(group.members[i].name, testGroup2.members[i].name);
expect(group.members[i].createdAt, testGroup2.members[i].createdAt);
}
} }
}); });
}); });

View File

@@ -81,7 +81,7 @@ void main() {
}); });
test('Adding and fetching multiple groups works correctly', () async { test('Adding and fetching multiple groups works correctly', () async {
await database.groupDao.addGroups( await database.groupDao.addGroupsAsList(
groups: [testGroup1, testGroup2, testGroup3, testGroup4], groups: [testGroup1, testGroup2, testGroup3, testGroup4],
); );

View File

@@ -136,5 +136,49 @@ void main() {
expect(players[i].createdAt, testGameOnlyPlayers.players![i].createdAt); expect(players[i].createdAt, testGameOnlyPlayers.players![i].createdAt);
} }
}); });
test('Updating the games players works coreclty', () async {
await database.gameDao.addGame(game: testGameOnlyPlayers);
final newPlayers = [testPlayer1, testPlayer2, testPlayer4];
await database.playerDao.addPlayersAsList(players: newPlayers);
// First, remove all existing players
final existingPlayers = await database.playerGameDao.getPlayersOfGame(
gameId: testGameOnlyPlayers.id,
);
print('existingPlayers: $existingPlayers');
if (existingPlayers == null || existingPlayers.isEmpty) {
fail('Existing players should not be null or empty');
}
await database.playerGameDao.updatePlayersFromGame(
gameId: testGameOnlyPlayers.id,
newPlayer: newPlayers,
);
final updatedPlayers = await database.playerGameDao.getPlayersOfGame(
gameId: testGameOnlyPlayers.id,
);
if (updatedPlayers == null) {
fail('Updated players should not be null');
}
expect(updatedPlayers.length, newPlayers.length);
/// Create a map of new players for easy lookup
final testPlayers = {for (var p in newPlayers) p.id: p};
/// Verify each updated player matches the new players
for (final player in updatedPlayers) {
final testPlayer = testPlayers[player.id]!;
expect(player.id, testPlayer.id);
expect(player.name, testPlayer.name);
expect(player.createdAt, testPlayer.createdAt);
}
});
}); });
} }

View File

@@ -56,7 +56,7 @@ void main() {
}); });
test('Adding and fetching multiple players works correctly', () async { test('Adding and fetching multiple players works correctly', () async {
await database.playerDao.addPlayers( await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
); );