Merge branch 'development' into feature/119-implementierung-der-games-2

# Conflicts:
#	lib/data/dao/match_dao.dart
This commit is contained in:
2026-05-05 11:40:32 +02:00
33 changed files with 3302 additions and 3553 deletions

View File

@@ -10,39 +10,7 @@ part 'game_dao.g.dart';
class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin { class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
GameDao(super.db); GameDao(super.db);
/// Retrieves all games from the database. /* Create */
Future<List<Game>> getAllGames() async {
final query = select(gameTable);
final result = await query.get();
return result
.map(
(row) => Game(
id: row.id,
name: row.name,
ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset),
description: row.description,
color: GameColor.values.firstWhere((e) => e.name == row.color),
icon: row.icon,
createdAt: row.createdAt,
),
)
.toList();
}
/// Retrieves a [Game] by its [gameId].
Future<Game> getGameById({required String gameId}) async {
final query = select(gameTable)..where((g) => g.id.equals(gameId));
final result = await query.getSingle();
return Game(
id: result.id,
name: result.name,
ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset),
description: result.description,
color: GameColor.values.firstWhere((e) => e.name == result.color),
icon: result.icon,
createdAt: result.createdAt,
);
}
/// Adds a new [game] to the database. /// Adds a new [game] to the database.
/// If a game with the same ID already exists, no action is taken. /// If a game with the same ID already exists, no action is taken.
@@ -94,12 +62,15 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
return true; return true;
} }
/// Deletes the game with the given [gameId] from the database. /* Read */
/// Returns `true` if the game was deleted, `false` if the game did not exist.
Future<bool> deleteGame({required String gameId}) async { /// Retrieves the total count of games in the database.
final query = delete(gameTable)..where((g) => g.id.equals(gameId)); Future<int> getGameCount() async {
final rowsAffected = await query.go(); final count =
return rowsAffected > 0; await (selectOnly(gameTable)..addColumns([gameTable.id.count()]))
.map((row) => row.read(gameTable.id.count()))
.getSingle();
return count ?? 0;
} }
/// Checks if a game with the given [gameId] exists in the database. /// Checks if a game with the given [gameId] exists in the database.
@@ -110,63 +81,110 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
return result != null; return result != null;
} }
/// Updates the name of the game with the given [gameId] to [newName]. /// Retrieves all games from the database.
Future<void> updateGameName({ Future<List<Game>> getAllGames() async {
required String gameId, final query = select(gameTable);
required String newName, final result = await query.get();
}) async { return result
await (update(gameTable)..where((g) => g.id.equals(gameId))).write( .map(
GameTableCompanion(name: Value(newName)), (row) => Game(
id: row.id,
name: row.name,
ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset),
description: row.description,
color: GameColor.values.firstWhere((e) => e.name == row.color),
icon: row.icon,
createdAt: row.createdAt,
),
)
.toList();
}
/// Retrieves a [Game] by its [gameId].
Future<Game> getGameById({required String gameId}) async {
final query = select(gameTable)..where((g) => g.id.equals(gameId));
final result = await query.getSingle();
return Game(
id: result.id,
name: result.name,
ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset),
description: result.description,
color: GameColor.values.firstWhere((e) => e.name == result.color),
icon: result.icon,
createdAt: result.createdAt,
); );
} }
/* Update */
/// Updates the name of the game with the given [gameId] to [name].
Future<bool> updateGameName({
required String gameId,
required String name,
}) async {
final rowsAffected =
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
GameTableCompanion(name: Value(name)),
);
return rowsAffected > 0;
}
/// Updates the ruleset of the game with the given [gameId]. /// Updates the ruleset of the game with the given [gameId].
Future<void> updateGameRuleset({ Future<bool> updateGameRuleset({
required String gameId, required String gameId,
required Ruleset newRuleset, required Ruleset ruleset,
}) async { }) async {
await (update(gameTable)..where((g) => g.id.equals(gameId))).write( final rowsAffected =
GameTableCompanion(ruleset: Value(newRuleset.name)), await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
); GameTableCompanion(ruleset: Value(ruleset.name)),
);
return rowsAffected > 0;
} }
/// Updates the description of the game with the given [gameId]. /// Updates the description of the game with the given [gameId].
Future<void> updateGameDescription({ Future<bool> updateGameDescription({
required String gameId, required String gameId,
required String newDescription, required String description,
}) async { }) async {
await (update(gameTable)..where((g) => g.id.equals(gameId))).write( final rowsAffected =
GameTableCompanion(description: Value(newDescription)), await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
); GameTableCompanion(description: Value(description)),
);
return rowsAffected > 0;
} }
/// Updates the color of the game with the given [gameId]. /// Updates the color of the game with the given [gameId].
Future<void> updateGameColor({ Future<bool> updateGameColor({
required String gameId, required String gameId,
required GameColor newColor, required GameColor color,
}) async { }) async {
await (update(gameTable)..where((g) => g.id.equals(gameId))).write( final rowsAffected =
GameTableCompanion(color: Value(newColor.name)), await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
); GameTableCompanion(color: Value(color.name)),
);
return rowsAffected > 0;
} }
/// Updates the icon of the game with the given [gameId]. /// Updates the icon of the game with the given [gameId].
Future<void> updateGameIcon({ Future<bool> updateGameIcon({
required String gameId, required String gameId,
required String newIcon, required String icon,
}) async { }) async {
await (update(gameTable)..where((g) => g.id.equals(gameId))).write( final rowsAffected =
GameTableCompanion(icon: Value(newIcon)), await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
); GameTableCompanion(icon: Value(icon)),
);
return rowsAffected > 0;
} }
/// Retrieves the total count of games in the database. /* Delete */
Future<int> getGameCount() async {
final count = /// Deletes the game with the given [gameId] from the database.
await (selectOnly(gameTable)..addColumns([gameTable.id.count()])) /// Returns `true` if the game was deleted, `false` if the game did not exist.
.map((row) => row.read(gameTable.id.count())) Future<bool> deleteGame({required String gameId}) async {
.getSingle(); final query = delete(gameTable)..where((g) => g.id.equals(gameId));
return count ?? 0; final rowsAffected = await query.go();
return rowsAffected > 0;
} }
/// Deletes all games from the database. /// Deletes all games from the database.

View File

@@ -12,43 +12,7 @@ part 'group_dao.g.dart';
class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin { class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
GroupDao(super.db); GroupDao(super.db);
/// Retrieves all groups from the database. /* Create */
Future<List<Group>> getAllGroups() async {
final query = select(groupTable);
final result = await query.get();
return Future.wait(
result.map((groupData) async {
final members = await db.playerGroupDao.getPlayersOfGroup(
groupId: groupData.id,
);
return Group(
id: groupData.id,
name: groupData.name,
description: groupData.description,
members: members,
createdAt: groupData.createdAt,
);
}),
);
}
/// Retrieves a [Group] by its [groupId], including its members.
Future<Group> getGroupById({required String groupId}) async {
final query = select(groupTable)..where((g) => g.id.equals(groupId));
final result = await query.getSingle();
List<Player> members = await db.playerGroupDao.getPlayersOfGroup(
groupId: groupId,
);
return Group(
id: result.id,
name: result.name,
description: result.description,
members: members,
createdAt: result.createdAt,
);
}
/// Adds a new group with the given [id] and [name] to the database. /// Adds a new group with the given [id] and [name] to the database.
/// This method also adds the group's members to the [PlayerGroupTable]. /// This method also adds the group's members to the [PlayerGroupTable].
@@ -172,38 +136,44 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
}); });
} }
/// Deletes the group with the given [id] from the database. /* Read */
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteGroup({required String groupId}) async { /// Retrieves all groups from the database.
final query = (delete(groupTable)..where((g) => g.id.equals(groupId))); Future<List<Group>> getAllGroups() async {
final rowsAffected = await query.go(); final query = select(groupTable);
return rowsAffected > 0; final result = await query.get();
return Future.wait(
result.map((groupData) async {
final members = await db.playerGroupDao.getPlayersOfGroup(
groupId: groupData.id,
);
return Group(
id: groupData.id,
name: groupData.name,
description: groupData.description,
members: members,
createdAt: groupData.createdAt,
);
}),
);
} }
/// Updates the name of the group with the given [id] to [newName]. /// Retrieves a [Group] by its [groupId], including its members.
/// Returns `true` if more than 0 rows were affected, otherwise `false`. Future<Group> getGroupById({required String groupId}) async {
Future<bool> updateGroupName({ final query = select(groupTable)..where((g) => g.id.equals(groupId));
required String groupId, final result = await query.getSingle();
required String newName,
}) async {
final rowsAffected =
await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
GroupTableCompanion(name: Value(newName)),
);
return rowsAffected > 0;
}
/// Updates the description of the group with the given [groupId] to [newDescription]. List<Player> members = await db.playerGroupDao.getPlayersOfGroup(
/// Returns `true` if more than 0 rows were affected, otherwise `false`. groupId: groupId,
Future<bool> updateGroupDescription({ );
required String groupId,
required String newDescription, return Group(
}) async { id: result.id,
final rowsAffected = name: result.name,
await (update(groupTable)..where((g) => g.id.equals(groupId))).write( description: result.description,
GroupTableCompanion(description: Value(newDescription)), members: members,
); createdAt: result.createdAt,
return rowsAffected > 0; );
} }
/// Retrieves the number of groups in the database. /// Retrieves the number of groups in the database.
@@ -223,6 +193,16 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
return result != null; return result != null;
} }
/* Delete */
/// Deletes the group with the given [id] from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteGroup({required String groupId}) async {
final query = (delete(groupTable)..where((g) => g.id.equals(groupId)));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Deletes all groups from the database. /// Deletes all groups from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`. /// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteAllGroups() async { Future<bool> deleteAllGroups() async {
@@ -231,47 +211,31 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Replaces all players in a group with the provided list of players. /* Update */
/// Removes all existing players from the group and adds the new players.
/// Also adds any new players to the player table if they don't exist. /// Updates the name of the group with the given [id] to [name].
/// Returns `true` if the group exists and players were replaced, `false` otherwise. /// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> replaceGroupPlayers({ Future<bool> updateGroupName({
required String groupId, required String groupId,
required List<Player> newPlayers, required String name,
}) async { }) async {
if (!await groupExists(groupId: groupId)) return false; final rowsAffected =
await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
GroupTableCompanion(name: Value(name)),
);
return rowsAffected > 0;
}
await db.transaction(() async { /// Updates the description of the group with the given [groupId] to [description].
// Remove all existing players from the group /// Returns `true` if more than 0 rows were affected, otherwise `false`.
final deleteQuery = delete(db.playerGroupTable) Future<bool> updateGroupDescription({
..where((p) => p.groupId.equals(groupId)); required String groupId,
await deleteQuery.go(); required String description,
}) async {
// Add new players to the player table if they don't exist final rowsAffected =
await Future.wait( await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
newPlayers.map((player) async { GroupTableCompanion(description: Value(description)),
if (!await db.playerDao.playerExists(playerId: player.id)) { );
await db.playerDao.addPlayer(player: player); return rowsAffected > 0;
}
}),
);
// Add the new players to the group
await db.batch(
(b) => b.insertAll(
db.playerGroupTable,
newPlayers
.map(
(player) => PlayerGroupTableCompanion.insert(
playerId: player.id,
groupId: groupId,
),
)
.toList(),
mode: InsertMode.insertOrReplace,
),
);
});
return true;
} }
} }

View File

@@ -8,6 +8,7 @@ import 'package:tallee/data/models/game.dart';
import 'package:tallee/data/models/group.dart'; import 'package:tallee/data/models/group.dart';
import 'package:tallee/data/models/match.dart'; import 'package:tallee/data/models/match.dart';
import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/team.dart';
part 'match_dao.g.dart'; part 'match_dao.g.dart';
@@ -15,74 +16,13 @@ part 'match_dao.g.dart';
class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin { class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
MatchDao(super.db); MatchDao(super.db);
/// Retrieves all matches from the database. /* Create */
Future<List<Match>> getAllMatches() async {
final query = select(matchTable);
final result = await query.get();
return Future.wait( /// Adds a new [Match] to the database. Also adds players associations and teams.
result.map((row) async {
final game = await db.gameDao.getGameById(gameId: row.gameId);
Group? group;
if (row.groupId != null) {
group = await db.groupDao.getGroupById(groupId: row.groupId!);
}
final players =
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? [];
final scores = await db.scoreEntryDao.getAllMatchScores(
matchId: row.id,
);
return Match(
id: row.id,
name: row.name,
game: game,
group: group,
players: players,
notes: row.notes ?? '',
createdAt: row.createdAt,
endedAt: row.endedAt,
scores: scores,
);
}),
);
}
/// Retrieves a [Match] by its [matchId].
Future<Match> getMatchById({required String matchId}) async {
final query = select(matchTable)..where((g) => g.id.equals(matchId));
final result = await query.getSingle();
final game = await db.gameDao.getGameById(gameId: result.gameId);
Group? group;
if (result.groupId != null) {
group = await db.groupDao.getGroupById(groupId: result.groupId!);
}
final players =
await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? [];
final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId);
return Match(
id: result.id,
name: result.name,
game: game,
group: group,
players: players,
notes: result.notes ?? '',
createdAt: result.createdAt,
endedAt: result.endedAt,
scores: scores,
);
}
/// Adds a new [Match] to the database. Also adds players associations.
/// This method assumes that the game and group (if any) are already present /// This method assumes that the game and group (if any) are already present
/// in the database. /// in the database.
Future<void> addMatch({required Match match}) async { Future<bool> addMatch({required Match match}) async {
if (await matchExists(matchId: match.id)) return false;
await db.transaction(() async { await db.transaction(() async {
await into(matchTable).insert( await into(matchTable).insert(
MatchTableCompanion.insert( MatchTableCompanion.insert(
@@ -90,18 +30,36 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
gameId: match.game.id, gameId: match.game.id,
groupId: Value(match.group?.id), groupId: Value(match.group?.id),
name: match.name, name: match.name,
notes: Value(match.notes), notes: match.notes,
createdAt: match.createdAt, createdAt: match.createdAt,
endedAt: Value(match.endedAt), endedAt: Value(match.endedAt),
), ),
mode: InsertMode.insertOrReplace, mode: InsertMode.insertOrReplace,
); );
// Add teams
if (match.teams != null && match.teams!.isNotEmpty) {
await db.teamDao.addTeamsAsList(teams: match.teams!, matchId: match.id);
}
// Collect all player IDs that are already in teams
final playersInTeams = <String>{};
if (match.teams != null) {
for (final team in match.teams!) {
for (final member in team.members) {
playersInTeams.add(member.id);
}
}
}
// Add players that are not in teams
for (final p in match.players) { for (final p in match.players) {
await db.playerMatchDao.addPlayerToMatch( if (!playersInTeams.contains(p.id)) {
matchId: match.id, await db.playerMatchDao.addPlayerToMatch(
playerId: p.id, matchId: match.id,
); playerId: p.id,
);
}
} }
for (final pid in match.scores.keys) { for (final pid in match.scores.keys) {
@@ -115,14 +73,15 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
} }
} }
}); });
return true;
} }
/// Adds multiple [Match]es to the database in a batch operation. /// Adds multiple [Match]es to the database in a batch operation.
/// Also adds associated players and groups if they exist. /// Also adds associated players and groups if they exist.
/// If the [matches] list is empty, the method returns immediately. /// If the [matches] list is empty, the method returns immediately.
/// This method should only be used to import matches from a different device. /// This method should only be used to import matches from a different device.
Future<void> addMatchAsList({required List<Match> matches}) async { Future<bool> addMatchesAsList({required List<Match> matches}) async {
if (matches.isEmpty) return; if (matches.isEmpty) return false;
await db.transaction(() async { await db.transaction(() async {
// Add all games first (deduplicated) // Add all games first (deduplicated)
final uniqueGames = <String, Game>{}; final uniqueGames = <String, Game>{};
@@ -183,7 +142,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
gameId: match.game.id, gameId: match.game.id,
groupId: Value(match.group?.id), groupId: Value(match.group?.id),
name: match.name, name: match.name,
notes: Value(match.notes), notes: match.notes,
createdAt: match.createdAt, createdAt: match.createdAt,
endedAt: Value(match.endedAt), endedAt: Value(match.endedAt),
), ),
@@ -279,15 +238,28 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
} }
} }
}); });
// Add teams for matches
for (final match in matches) {
if (match.teams != null && match.teams!.isNotEmpty) {
await db.teamDao.addTeamsAsList(
teams: match.teams!,
matchId: match.id,
);
}
}
}); });
return true;
} }
/// Deletes the match with the given [matchId] from the database. /* Read */
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteMatch({required String matchId}) async { /// Checks if a match with the given [matchId] exists in the database.
final query = delete(matchTable)..where((g) => g.id.equals(matchId)); /// Returns `true` if the match exists, otherwise `false`.
final rowsAffected = await query.go(); Future<bool> matchExists({required String matchId}) async {
return rowsAffected > 0; final query = select(matchTable)..where((g) => g.id.equals(matchId));
final result = await query.getSingleOrNull();
return result != null;
} }
/// Retrieves the number of matches in the database. /// Retrieves the number of matches in the database.
@@ -299,6 +271,76 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
return count ?? 0; return count ?? 0;
} }
/// Retrieves all matches from the database.
Future<List<Match>> getAllMatches() async {
final query = select(matchTable);
final result = await query.get();
return Future.wait(
result.map((row) async {
final game = await db.gameDao.getGameById(gameId: row.gameId);
Group? group;
if (row.groupId != null) {
group = await db.groupDao.getGroupById(groupId: row.groupId!);
}
final players = await db.playerMatchDao.getPlayersOfMatch(
matchId: row.id,
);
final scores = await db.scoreEntryDao.getAllMatchScores(
matchId: row.id,
);
final teams = await _getMatchTeams(matchId: row.id);
return Match(
id: row.id,
name: row.name,
game: game,
group: group,
players: players,
teams: teams.isEmpty ? null : teams,
notes: row.notes,
createdAt: row.createdAt,
endedAt: row.endedAt,
scores: scores,
);
}),
);
}
/// Retrieves a [Match] by its [matchId].
Future<Match> getMatchById({required String matchId}) async {
final query = select(matchTable)..where((g) => g.id.equals(matchId));
final result = await query.getSingle();
final game = await db.gameDao.getGameById(gameId: result.gameId);
Group? group;
if (result.groupId != null) {
group = await db.groupDao.getGroupById(groupId: result.groupId!);
}
final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId);
final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId);
final teams = await _getMatchTeams(matchId: matchId);
return Match(
id: result.id,
name: result.name,
game: game,
group: group,
players: players,
teams: teams.isEmpty ? null : teams,
notes: result.notes,
createdAt: result.createdAt,
endedAt: result.endedAt,
scores: scores,
);
}
/// Retrieves the number of matches associated with a specific game. /// Retrieves the number of matches associated with a specific game.
Future<int> getMatchCountByGame({required String gameId}) async { Future<int> getMatchCountByGame({required String gameId}) async {
final count = final count =
@@ -310,14 +352,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
return count ?? 0; return count ?? 0;
} }
/// Deletes all matches associated with a specific game.
/// Returns the number of matches deleted.
Future<int> deleteMatchesByGame({required String gameId}) async {
final query = delete(matchTable)..where((m) => m.gameId.equals(gameId));
final rowsAffected = await query.go();
return rowsAffected;
}
/// Retrieves all matches associated with the given [groupId]. /// Retrieves all matches associated with the given [groupId].
/// Queries the database directly, filtering by [groupId]. /// Queries the database directly, filtering by [groupId].
Future<List<Match>> getGroupMatches({required String groupId}) async { Future<List<Match>> getGroupMatches({required String groupId}) async {
@@ -328,15 +362,18 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
rows.map((row) async { rows.map((row) async {
final game = await db.gameDao.getGameById(gameId: row.gameId); final game = await db.gameDao.getGameById(gameId: row.gameId);
final group = await db.groupDao.getGroupById(groupId: groupId); final group = await db.groupDao.getGroupById(groupId: groupId);
final players = final players = await db.playerMatchDao.getPlayersOfMatch(
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; matchId: row.id,
);
final teams = await _getMatchTeams(matchId: row.id);
return Match( return Match(
id: row.id, id: row.id,
name: row.name, name: row.name,
game: game, game: game,
group: group, group: group,
players: players, players: players,
notes: row.notes ?? '', teams: teams.isEmpty ? null : teams,
notes: row.notes,
createdAt: row.createdAt, createdAt: row.createdAt,
endedAt: row.endedAt, endedAt: row.endedAt,
); );
@@ -344,19 +381,56 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
); );
} }
/// Checks if a match with the given [matchId] exists in the database. /// Helper method to retrieve teams for a specific match
/// Returns `true` if the match exists, otherwise `false`. Future<List<Team>> _getMatchTeams({required String matchId}) async {
Future<bool> matchExists({required String matchId}) async { // Get all unique team IDs from PlayerMatchTable for this match
final query = select(matchTable)..where((g) => g.id.equals(matchId)); final playerMatchQuery = select(db.playerMatchTable)
final result = await query.getSingleOrNull(); ..where((pm) => pm.matchId.equals(matchId) & pm.teamId.isNotNull());
return result != null; final playerMatches = await playerMatchQuery.get();
if (playerMatches.isEmpty) return [];
final teamIds = playerMatches
.map((pm) => pm.teamId)
.whereType<String>()
.toSet()
.toList();
// Fetch all teams
final teams = await Future.wait(
teamIds.map((teamId) => db.teamDao.getTeamById(teamId: teamId)),
);
return teams;
} }
/// Deletes all matches from the database. /* Update */
/// Changes the name of the match with the given [matchId] to [name].
/// Returns `true` if more than 0 rows were affected, otherwise `false`. /// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteAllMatches() async { Future<bool> updateMatchName({
final query = delete(matchTable); required String matchId,
final rowsAffected = await query.go(); required String name,
}) async {
final query = update(matchTable)..where((g) => g.id.equals(matchId));
final rowsAffected = await query.write(
MatchTableCompanion(name: Value(name)),
);
return rowsAffected > 0;
}
/// Updates the group of the match with the given [matchId].
/// Replaces the existing group association with the new group specified by [groupId].
/// Pass null to remove the group association.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateMatchGroup({
required String matchId,
required String? groupId,
}) async {
final query = update(matchTable)..where((g) => g.id.equals(matchId));
final rowsAffected = await query.write(
MatchTableCompanion(groupId: Value(groupId)),
);
return rowsAffected > 0; return rowsAffected > 0;
} }
@@ -364,7 +438,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
/// Returns `true` if more than 0 rows were affected, otherwise `false`. /// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateMatchNotes({ Future<bool> updateMatchNotes({
required String matchId, required String matchId,
required String? notes, required String notes,
}) async { }) async {
final query = update(matchTable)..where((g) => g.id.equals(matchId)); final query = update(matchTable)..where((g) => g.id.equals(matchId));
final rowsAffected = await query.write( final rowsAffected = await query.write(
@@ -373,47 +447,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Changes the name of the match with the given [matchId] to [newName].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateMatchName({
required String matchId,
required String newName,
}) async {
final query = update(matchTable)..where((g) => g.id.equals(matchId));
final rowsAffected = await query.write(
MatchTableCompanion(name: Value(newName)),
);
return rowsAffected > 0;
}
/// Updates the game of the match with the given [matchId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateMatchGame({
required String matchId,
required String gameId,
}) async {
final query = update(matchTable)..where((g) => g.id.equals(matchId));
final rowsAffected = await query.write(
MatchTableCompanion(gameId: Value(gameId)),
);
return rowsAffected > 0;
}
/// Updates the group of the match with the given [matchId].
/// Replaces the existing group association with the new group specified by [newGroupId].
/// Pass null to remove the group association.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateMatchGroup({
required String matchId,
required String? newGroupId,
}) async {
final query = update(matchTable)..where((g) => g.id.equals(matchId));
final rowsAffected = await query.write(
MatchTableCompanion(groupId: Value(newGroupId)),
);
return rowsAffected > 0;
}
/// Removes the group association of the match with the given [matchId]. /// Removes the group association of the match with the given [matchId].
/// Sets the groupId to null. /// Sets the groupId to null.
/// Returns `true` if more than 0 rows were affected, otherwise `false`. /// Returns `true` if more than 0 rows were affected, otherwise `false`.
@@ -425,25 +458,12 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Updates the createdAt timestamp of the match with the given [matchId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateMatchCreatedAt({
required String matchId,
required DateTime createdAt,
}) async {
final query = update(matchTable)..where((g) => g.id.equals(matchId));
final rowsAffected = await query.write(
MatchTableCompanion(createdAt: Value(createdAt)),
);
return rowsAffected > 0;
}
/// Updates the endedAt timestamp of the match with the given [matchId]. /// Updates the endedAt timestamp of the match with the given [matchId].
/// Pass null to remove the ended time (mark match as ongoing). /// Pass null to remove the ended time (mark match as ongoing).
/// Returns `true` if more than 0 rows were affected, otherwise `false`. /// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> updateMatchEndedAt({ Future<bool> updateMatchEndedAt({
required String matchId, required String matchId,
required DateTime? endedAt, required DateTime endedAt,
}) async { }) async {
final query = update(matchTable)..where((g) => g.id.equals(matchId)); final query = update(matchTable)..where((g) => g.id.equals(matchId));
final rowsAffected = await query.write( final rowsAffected = await query.write(
@@ -452,37 +472,29 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Replaces all players in a match with the provided list of players. /* Delete */
/// Removes all existing players from the match and adds the new players.
/// Also adds any new players to the player table if they don't exist.
Future<void> replaceMatchPlayers({
required String matchId,
required List<Player> newPlayers,
}) async {
await db.transaction(() async {
// Remove all existing players from the match
final deleteQuery = delete(db.playerMatchTable)
..where((p) => p.matchId.equals(matchId));
await deleteQuery.go();
// Add new players to the player table if they don't exist /// Deletes the match with the given [matchId] from the database.
await Future.wait( /// Returns `true` if more than 0 rows were affected, otherwise `false`.
newPlayers.map((player) async { Future<bool> deleteMatch({required String matchId}) async {
if (!await db.playerDao.playerExists(playerId: player.id)) { final query = delete(matchTable)..where((g) => g.id.equals(matchId));
await db.playerDao.addPlayer(player: player); final rowsAffected = await query.go();
} return rowsAffected > 0;
}), }
);
// Add the new players to the match /// Deletes all matches from the database.
await Future.wait( /// Returns `true` if more than 0 rows were affected, otherwise `false`.
newPlayers.map( Future<bool> deleteAllMatches() async {
(player) => db.playerMatchDao.addPlayerToMatch( final query = delete(matchTable);
matchId: matchId, final rowsAffected = await query.go();
playerId: player.id, return rowsAffected > 0;
), }
),
); /// Deletes all matches associated with a specific game.
}); /// Returns the number of matches deleted.
Future<int> deleteMatchesByGame({required String gameId}) async {
final query = delete(matchTable)..where((m) => m.gameId.equals(gameId));
final rowsAffected = await query.go();
return rowsAffected;
} }
} }

View File

@@ -10,35 +10,7 @@ part 'player_dao.g.dart';
class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin { class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
PlayerDao(super.db); PlayerDao(super.db);
/// Retrieves all players from the database. /* Create */
Future<List<Player>> getAllPlayers() async {
final query = select(playerTable);
final result = await query.get();
return result
.map(
(row) => Player(
id: row.id,
name: row.name,
description: row.description,
createdAt: row.createdAt,
nameCount: row.nameCount,
),
)
.toList();
}
/// Retrieves a [Player] by their [id].
Future<Player> getPlayerById({required String playerId}) async {
final query = select(playerTable)..where((p) => p.id.equals(playerId));
final result = await query.getSingle();
return Player(
id: result.id,
name: result.name,
description: result.description,
createdAt: result.createdAt,
nameCount: result.nameCount,
);
}
/// Adds a new [player] to the database. /// Adds a new [player] to the database.
/// If a player with the same ID already exists, updates their name to /// If a player with the same ID already exists, updates their name to
@@ -135,12 +107,15 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
return true; return true;
} }
/// Deletes the player with the given [id] from the database. /* Read */
/// Returns `true` if the player was deleted, `false` if the player did not exist.
Future<bool> deletePlayer({required String playerId}) async { /// Retrieves the total count of players in the database.
final query = delete(playerTable)..where((p) => p.id.equals(playerId)); Future<int> getPlayerCount() async {
final rowsAffected = await query.go(); final count =
return rowsAffected > 0; await (selectOnly(playerTable)..addColumns([playerTable.id.count()]))
.map((row) => row.read(playerTable.id.count()))
.getSingle();
return count ?? 0;
} }
/// Checks if a player with the given [playerId] exists in the database. /// Checks if a player with the given [playerId] exists in the database.
@@ -151,10 +126,42 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
return result != null; return result != null;
} }
/// Updates the name of the player with the given [playerId] to [newName]. /// Retrieves all players from the database.
Future<void> updatePlayerName({ Future<List<Player>> getAllPlayers() async {
final query = select(playerTable);
final result = await query.get();
return result
.map(
(row) => Player(
id: row.id,
name: row.name,
description: row.description,
createdAt: row.createdAt,
nameCount: row.nameCount,
),
)
.toList();
}
/// Retrieves a [Player] by their [id].
Future<Player> getPlayerById({required String playerId}) async {
final query = select(playerTable)..where((p) => p.id.equals(playerId));
final result = await query.getSingle();
return Player(
id: result.id,
name: result.name,
description: result.description,
createdAt: result.createdAt,
nameCount: result.nameCount,
);
}
/* Update */
/// Updates the name of the player with the given [playerId] to [name].
Future<bool> updatePlayerName({
required String playerId, required String playerId,
required String newName, required String name,
}) async { }) async {
// Get previous name and name count for the player before updating // Get previous name and name count for the player before updating
final previousPlayerName = final previousPlayerName =
@@ -164,14 +171,15 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
''; '';
final previousNameCount = await getNameCount(name: previousPlayerName); final previousNameCount = await getNameCount(name: previousPlayerName);
await (update(playerTable)..where((p) => p.id.equals(playerId))).write( final rowsAffected =
PlayerTableCompanion(name: Value(newName)), await (update(playerTable)..where((p) => p.id.equals(playerId))).write(
); PlayerTableCompanion(name: Value(name)),
);
// Update name count for the new name // Update name count for the new name
final count = await calculateNameCount(name: newName); final count = await calculateNameCount(name: name);
if (count > 0) { if (count > 0) {
await (update(playerTable)..where((p) => p.name.equals(newName))).write( await (update(playerTable)..where((p) => p.name.equals(name))).write(
PlayerTableCompanion(nameCount: Value(count)), PlayerTableCompanion(nameCount: Value(count)),
); );
} }
@@ -188,17 +196,35 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
); );
} }
} }
return rowsAffected > 0;
} }
/// Retrieves the total count of players in the database. /// Updates the description of the player with the given [playerId] to
Future<int> getPlayerCount() async { /// [description].
final count = /// Returns `true` if more than 0 rows were affected, otherwise `false`.
await (selectOnly(playerTable)..addColumns([playerTable.id.count()])) Future<bool> updatePlayerDescription({
.map((row) => row.read(playerTable.id.count())) required String playerId,
.getSingle(); required String description,
return count ?? 0; }) async {
final rowsAffected =
await (update(playerTable)..where((g) => g.id.equals(playerId))).write(
PlayerTableCompanion(description: Value(description)),
);
return rowsAffected > 0;
} }
/* Delete */
/// Deletes the player with the given [id] from the database.
/// Returns `true` if the player was deleted, `false` if the player did not exist.
Future<bool> deletePlayer({required String playerId}) async {
final query = delete(playerTable)..where((p) => p.id.equals(playerId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/* Name count management */
/// Retrieves the count of players with the given [name]. /// Retrieves the count of players with the given [name].
Future<int> getNameCount({required String name}) async { Future<int> getNameCount({required String name}) async {
final query = select(playerTable)..where((p) => p.name.equals(name)); final query = select(playerTable)..where((p) => p.name.equals(name));

View File

@@ -11,8 +11,7 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
with _$PlayerGroupDaoMixin { with _$PlayerGroupDaoMixin {
PlayerGroupDao(super.db); PlayerGroupDao(super.db);
/// No need for a groupHasPlayers method since the members attribute is /* Create */
/// not nullable
/// Adds a [player] to a group with the given [groupId]. /// 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 is already in the group, no action is taken.
@@ -33,10 +32,11 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
await into(playerGroupTable).insert( await into(playerGroupTable).insert(
PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId),
); );
return true; return true;
} }
/* Read */
/// Retrieves all players belonging to a specific group by [groupId]. /// Retrieves all players belonging to a specific group by [groupId].
Future<List<Player>> getPlayersOfGroup({required String groupId}) async { Future<List<Player>> getPlayersOfGroup({required String groupId}) async {
final query = select(playerGroupTable) final query = select(playerGroupTable)
@@ -53,18 +53,6 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
return groupMembers; return groupMembers;
} }
/// Removes a player from a group based on [playerId] and [groupId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> removePlayerFromGroup({
required String playerId,
required String groupId,
}) async {
final query = delete(playerGroupTable)
..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Checks if a player with [playerId] is in the group with [groupId]. /// Checks if a player with [playerId] is in the group with [groupId].
/// Returns `true` if the player is in the group, otherwise `false`. /// Returns `true` if the player is in the group, otherwise `false`.
Future<bool> isPlayerInGroup({ Future<bool> isPlayerInGroup({
@@ -76,4 +64,65 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
final result = await query.getSingleOrNull(); final result = await query.getSingleOrNull();
return result != null; return result != null;
} }
/* Update */
/// Replaces all players in a group with the provided list of players.
/// Removes all existing players from the group and adds the new players.
/// Also adds any new players to the player table if they don't exist.
/// Returns `true` if the group exists and players were replaced, `false` otherwise.
Future<bool> replaceGroupPlayers({
required String groupId,
required List<Player> newPlayers,
}) async {
if (!await db.groupDao.groupExists(groupId: groupId)) return false;
if (newPlayers.isEmpty) return false;
await db.transaction(() async {
// Remove all existing players from the group
final deleteQuery = delete(db.playerGroupTable)
..where((p) => p.groupId.equals(groupId));
await deleteQuery.go();
// Add new players to the player table if they don't exist
await Future.wait(
newPlayers.map((player) async {
if (!await db.playerDao.playerExists(playerId: player.id)) {
await db.playerDao.addPlayer(player: player);
}
}),
);
// Add the new players to the group
await db.batch(
(b) => b.insertAll(
db.playerGroupTable,
newPlayers
.map(
(player) => PlayerGroupTableCompanion.insert(
playerId: player.id,
groupId: groupId,
),
)
.toList(),
mode: InsertMode.insertOrReplace,
),
);
});
return true;
}
/* Delete */
/// Removes a player from a group based on [playerId] and [groupId].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> removePlayerFromGroup({
required String playerId,
required String groupId,
}) async {
final query = delete(playerGroupTable)
..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
} }

View File

@@ -11,14 +11,16 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
with _$PlayerMatchDaoMixin { with _$PlayerMatchDaoMixin {
PlayerMatchDao(super.db); PlayerMatchDao(super.db);
/* Create */
/// Associates a player with a match by inserting a record into the /// Associates a player with a match by inserting a record into the
/// [PlayerMatchTable]. Optionally associates with a team and sets initial score. /// [PlayerMatchTable]. Optionally associates with a team and sets initial score.
Future<void> addPlayerToMatch({ Future<bool> addPlayerToMatch({
required String matchId, required String matchId,
required String playerId, required String playerId,
String? teamId, String? teamId,
}) async { }) async {
await into(playerMatchTable).insert( final rowsAffected = await into(playerMatchTable).insert(
PlayerMatchTableCompanion.insert( PlayerMatchTableCompanion.insert(
playerId: playerId, playerId: playerId,
matchId: matchId, matchId: matchId,
@@ -26,42 +28,14 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
), ),
mode: InsertMode.insertOrReplace, mode: InsertMode.insertOrReplace,
); );
}
/// Retrieves a list of [Player]s associated with the given [matchId].
/// Returns null if no players are found.
Future<List<Player>?> getPlayersOfMatch({required String matchId}) async {
final result = await (select(
playerMatchTable,
)..where((p) => p.matchId.equals(matchId))).get();
if (result.isEmpty) return null;
final futures = result.map(
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
);
final players = await Future.wait(futures);
return players;
}
/// Updates the team for a player in a match.
/// Returns `true` if the update was successful, otherwise `false`.
Future<bool> updatePlayerTeam({
required String matchId,
required String playerId,
required String? teamId,
}) async {
final rowsAffected =
await (update(playerMatchTable)..where(
(p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
))
.write(PlayerMatchTableCompanion(teamId: Value(teamId)));
return rowsAffected > 0; return rowsAffected > 0;
} }
/* Read */
/// Checks if there are any players associated with the given [matchId]. /// Checks if there are any players associated with the given [matchId].
/// Returns `true` if there are players, otherwise `false`. /// Returns `true` if there are players, otherwise `false`.
Future<bool> matchHasPlayers({required String matchId}) async { Future<bool> hasMatchPlayers({required String matchId}) async {
final count = final count =
await (selectOnly(playerMatchTable) await (selectOnly(playerMatchTable)
..where(playerMatchTable.matchId.equals(matchId)) ..where(playerMatchTable.matchId.equals(matchId))
@@ -87,31 +61,79 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
return (count ?? 0) > 0; return (count ?? 0) > 0;
} }
/// Removes the association of a player with a match by deleting the record /// Retrieves a list of [Player]s associated with the given [matchId].
/// from the [PlayerMatchTable]. /// Returns empty list if no players are found.
/// Returns `true` if more than 0 rows were affected, otherwise `false`. Future<List<Player>> getPlayersOfMatch({required String matchId}) async {
Future<bool> removePlayerFromMatch({ final result = await (select(
playerMatchTable,
)..where((p) => p.matchId.equals(matchId))).get();
if (result.isEmpty) return [];
final futures = result.map(
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
);
final players = await Future.wait(futures);
return players;
}
/// Retrieves a list of [Player]s associated with a specific team in a match.
/// Returns empty list if no players are found for the team in the match.
Future<List<Player>> getPlayersOfTeamInMatch({
required String matchId,
required String teamId,
}) async {
final result =
await (select(playerMatchTable)
..where((p) => p.matchId.equals(matchId))
..where((p) => p.teamId.equals(teamId)))
.get();
if (result.isEmpty) return [];
final futures = result.map(
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
);
final players = await Future.wait(futures);
return players;
}
/* Updated */
/// Updates the team for a player in a match.
/// Returns `true` if the update was successful, otherwise `false`.
Future<bool> updatePlayersTeam({
required String matchId, required String matchId,
required String playerId, required String playerId,
required String? teamId,
}) async { }) async {
final query = delete(playerMatchTable) final rowsAffected =
..where((pg) => pg.matchId.equals(matchId)) await (update(playerMatchTable)..where(
..where((pg) => pg.playerId.equals(playerId)); (p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
final rowsAffected = await query.go(); ))
.write(PlayerMatchTableCompanion(teamId: Value(teamId)));
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Updates the players associated with a match based on the provided /// Updates the players associated with a match based on the provided
/// [newPlayer] list. It adds new players and removes players that are no /// [player] list. It adds new players and removes players that are no
/// longer associated with the match. /// longer associated with the match.
Future<void> updatePlayersFromMatch({ Future<bool> updateMatchPlayers({
required String matchId, required String matchId,
required List<Player> newPlayer, required List<Player> player,
}) async { }) async {
if (player.isEmpty) return false;
final currentPlayers = await getPlayersOfMatch(matchId: matchId); final currentPlayers = await getPlayersOfMatch(matchId: matchId);
// Create sets of player IDs for easy comparison // Create sets of player IDs for easy comparison
final currentPlayerIds = currentPlayers?.map((p) => p.id).toSet() ?? {}; final currentPlayerIds = currentPlayers.map((p) => p.id).toSet();
final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet(); final newPlayerIdsSet = player.map((p) => p.id).toSet();
// Are the current and new player identical?
if (currentPlayerIds.containsAll(newPlayerIdsSet) &&
newPlayerIdsSet.containsAll(currentPlayerIds)) {
return false;
}
// Determine players to add and remove // Determine players to add and remove
final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds); final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds);
@@ -147,22 +169,22 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
); );
} }
}); });
return true;
} }
/// Retrieves all players in a specific team for a match. /* Delete */
Future<List<Player>> getPlayersInTeam({
/// Removes the association of a player with a match by deleting the record
/// from the [PlayerMatchTable].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> removePlayerFromMatch({
required String matchId, required String matchId,
required String teamId, required String playerId,
}) async { }) async {
final result = await (select( final query = delete(playerMatchTable)
playerMatchTable, ..where((pg) => pg.matchId.equals(matchId))
)..where((p) => p.matchId.equals(matchId) & p.teamId.equals(teamId))).get(); ..where((pg) => pg.playerId.equals(playerId));
final rowsAffected = await query.go();
if (result.isEmpty) return []; return rowsAffected > 0;
final futures = result.map(
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
);
return Future.wait(futures);
} }
} }

View File

@@ -13,6 +13,8 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
with _$ScoreEntryDaoMixin { with _$ScoreEntryDaoMixin {
ScoreEntryDao(super.db); ScoreEntryDao(super.db);
/* Create */
/// Adds a score entry to the database. /// Adds a score entry to the database.
Future<void> addScore({ Future<void> addScore({
required String playerId, required String playerId,
@@ -58,6 +60,8 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
}); });
} }
/* Read */
/// Retrieves the score for a specific round. /// Retrieves the score for a specific round.
Future<ScoreEntry?> getScore({ Future<ScoreEntry?> getScore({
required String playerId, required String playerId,
@@ -126,28 +130,58 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
); );
} }
/// Gets the highest (latest) round number for a match.
/// Returns `null` if there are no scores for the match.
Future<int?> getLatestRoundNumber({required String matchId}) async {
final query = selectOnly(scoreEntryTable)
..where(scoreEntryTable.matchId.equals(matchId))
..addColumns([scoreEntryTable.roundNumber.max()]);
final result = await query.getSingle();
return result.read(scoreEntryTable.roundNumber.max());
}
/// Aggregates the total score for a player in a match by summing all their
/// score entry changes. Returns `0` if there are no scores for the player
/// in the match.
Future<int> getTotalScoreForPlayer({
required String playerId,
required String matchId,
}) async {
final scores = await getAllPlayerScoresInMatch(
playerId: playerId,
matchId: matchId,
);
if (scores.isEmpty) return 0;
// Return the sum of all score changes
return scores.fold<int>(0, (sum, element) => sum + element.change);
}
/* Update */
/// Updates a score entry. /// Updates a score entry.
Future<bool> updateScore({ Future<bool> updateScore({
required String playerId, required String playerId,
required String matchId, required String matchId,
required ScoreEntry newEntry, required ScoreEntry entry,
}) async { }) async {
final rowsAffected = final rowsAffected =
await (update(scoreEntryTable)..where( await (update(scoreEntryTable)..where(
(s) => (s) =>
s.playerId.equals(playerId) & s.playerId.equals(playerId) &
s.matchId.equals(matchId) & s.matchId.equals(matchId) &
s.roundNumber.equals(newEntry.roundNumber), s.roundNumber.equals(entry.roundNumber),
)) ))
.write( .write(
ScoreEntryTableCompanion( ScoreEntryTableCompanion(
score: Value(newEntry.score), score: Value(entry.score),
change: Value(newEntry.change), change: Value(entry.change),
), ),
); );
return rowsAffected > 0; return rowsAffected > 0;
} }
/* Delete */
/// Deletes a score entry. /// Deletes a score entry.
Future<bool> deleteScore({ Future<bool> deleteScore({
required String playerId, required String playerId,
@@ -182,31 +216,7 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Gets the highest (latest) round number for a match. /* Winner handling */
/// Returns `null` if there are no scores for the match.
Future<int?> getLatestRoundNumber({required String matchId}) async {
final query = selectOnly(scoreEntryTable)
..where(scoreEntryTable.matchId.equals(matchId))
..addColumns([scoreEntryTable.roundNumber.max()]);
final result = await query.getSingle();
return result.read(scoreEntryTable.roundNumber.max());
}
/// Aggregates the total score for a player in a match by summing all their
/// score entry changes. Returns `0` if there are no scores for the player
/// in the match.
Future<int> getTotalScoreForPlayer({
required String playerId,
required String matchId,
}) async {
final scores = await getAllPlayerScoresInMatch(
playerId: playerId,
matchId: matchId,
);
if (scores.isEmpty) return 0;
// Return the sum of all score changes
return scores.fold<int>(0, (sum, element) => sum + element.change);
}
Future<bool> hasWinner({required String matchId}) async { Future<bool> hasWinner({required String matchId}) async {
return await getWinner(matchId: matchId) != null; return await getWinner(matchId: matchId) != null;
@@ -275,12 +285,14 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
} }
} }
Future<bool> hasLooser({required String matchId}) async { /* Loser handling */
return await getLooser(matchId: matchId) != null;
Future<bool> hasLoser({required String matchId}) async {
return await getLoser(matchId: matchId) != null;
} }
// Setting the looser for a game and clearing previous looser if exists. // Setting the looser for a game and clearing previous looser if exists.
Future<bool> setLooser({ Future<bool> setLoser({
required String matchId, required String matchId,
required String playerId, required String playerId,
}) async { }) async {
@@ -304,7 +316,7 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
/// Retrieves the looser of a match by looking for a score entry where score /// Retrieves the looser of a match by looking for a score entry where score
/// is 0. Returns `null` if no player found, else the first with the score. /// is 0. Returns `null` if no player found, else the first with the score.
Future<Player?> getLooser({required String matchId}) async { Future<Player?> getLoser({required String matchId}) async {
final query = final query =
select(scoreEntryTable).join([ select(scoreEntryTable).join([
innerJoin( innerJoin(
@@ -332,7 +344,7 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
/// ///
/// Returns `true` if the looser was removed, `false` if there are multiple /// Returns `true` if the looser was removed, `false` if there are multiple
/// scores or if the looser cannot be removed. /// scores or if the looser cannot be removed.
Future<bool> removeLooser({required String matchId}) async { Future<bool> removeLoser({required String matchId}) async {
final scores = await getAllMatchScores(matchId: matchId); final scores = await getAllMatchScores(matchId: matchId);
if (scores.length > 1) { if (scores.length > 1) {

View File

@@ -1,17 +1,105 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/db/database.dart';
import 'package:tallee/data/db/tables/player_match_table.dart';
import 'package:tallee/data/db/tables/team_table.dart'; import 'package:tallee/data/db/tables/team_table.dart';
import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/team.dart'; import 'package:tallee/data/models/team.dart';
part 'team_dao.g.dart'; part 'team_dao.g.dart';
@DriftAccessor(tables: [TeamTable]) @DriftAccessor(tables: [TeamTable, PlayerMatchTable])
class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin { class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
TeamDao(super.db); TeamDao(super.db);
/* Create */
/// Adds a new [team] to the database.
/// Returns `true` if the team was added, `false` otherwise.
Future<bool> addTeam({required Team team, required String matchId}) async {
if (await teamExists(teamId: team.id)) return false;
await into(teamTable).insert(
TeamTableCompanion.insert(
id: team.id,
name: team.name,
createdAt: team.createdAt,
),
mode: InsertMode.insertOrReplace,
);
await db.batch((batch) async {
for (final player in team.members) {
await into(playerMatchTable).insert(
PlayerMatchTableCompanion.insert(
playerId: player.id,
matchId: matchId,
teamId: Value(team.id),
),
mode: InsertMode.insertOrReplace,
);
}
});
return true;
}
/// Adds multiple [teams] to the database in a batch operation.
Future<bool> addTeamsAsList({
required List<Team> teams,
required String matchId,
}) async {
if (teams.isEmpty) return false;
await db.batch(
(b) => b.insertAll(
teamTable,
teams
.map(
(team) => TeamTableCompanion.insert(
id: team.id,
name: team.name,
createdAt: team.createdAt,
),
)
.toList(),
mode: InsertMode.insertOrIgnore,
),
);
for (final team in teams) {
await db.batch((batch) async {
for (final player in team.members) {
await into(db.playerMatchTable).insert(
PlayerMatchTableCompanion.insert(
playerId: player.id,
matchId: matchId,
teamId: Value(team.id),
),
mode: InsertMode.insertOrReplace,
);
}
});
}
return true;
}
/* Read */
/// Retrieves the total count of teams in the database.
Future<int> getTeamCount() async {
final count =
await (selectOnly(teamTable)..addColumns([teamTable.id.count()]))
.map((row) => row.read(teamTable.id.count()))
.getSingle();
return count ?? 0;
}
/// Checks if a team with the given [teamId] exists in the database.
/// Returns `true` if the team exists, `false` otherwise.
Future<bool> teamExists({required String teamId}) async {
final query = select(teamTable)..where((t) => t.id.equals(teamId));
final result = await query.getSingleOrNull();
return result != null;
}
/// Retrieves all teams from the database. /// Retrieves all teams from the database.
/// Note: This returns teams without their members. Use getTeamById for full team data.
Future<List<Team>> getAllTeams() async { Future<List<Team>> getAllTeams() async {
final query = select(teamTable); final query = select(teamTable);
final result = await query.get(); final result = await query.get();
@@ -41,8 +129,7 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
); );
} }
/// Helper method to get team members from player_match_table. /// Helper method to get team members from PlayerMatchTable.
/// This assumes team members are tracked via the player_match_table.
Future<List<Player>> _getTeamMembers({required String teamId}) async { Future<List<Player>> _getTeamMembers({required String teamId}) async {
// Get all player_match entries with this teamId // Get all player_match entries with this teamId
final playerMatchQuery = select(db.playerMatchTable) final playerMatchQuery = select(db.playerMatchTable)
@@ -61,44 +148,28 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
return players; return players;
} }
/// Adds a new [team] to the database. /* Update */
/// Returns `true` if the team was added, `false` otherwise.
Future<bool> addTeam({required Team team}) async { /// Updates the name of the team with the given [teamId].
if (!await teamExists(teamId: team.id)) { Future<bool> updateTeamName({
await into(teamTable).insert( required String teamId,
TeamTableCompanion.insert( required String name,
id: team.id, }) async {
name: team.name, final rowsAffected =
createdAt: team.createdAt, await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
), TeamTableCompanion(name: Value(name)),
mode: InsertMode.insertOrReplace, );
); return rowsAffected > 0;
return true;
}
return false;
} }
/// Adds multiple [teams] to the database in a batch operation. /* Delete */
Future<bool> addTeamsAsList({required List<Team> teams}) async {
if (teams.isEmpty) return false;
await db.batch( /// Deletes all teams from the database.
(b) => b.insertAll( /// Returns `true` if more than 0 rows were affected, otherwise `false`.
teamTable, Future<bool> deleteAllTeams() async {
teams final query = delete(teamTable);
.map( final rowsAffected = await query.go();
(team) => TeamTableCompanion.insert( return rowsAffected > 0;
id: team.id,
name: team.name,
createdAt: team.createdAt,
),
)
.toList(),
mode: InsertMode.insertOrIgnore,
),
);
return true;
} }
/// Deletes the team with the given [teamId] from the database. /// Deletes the team with the given [teamId] from the database.
@@ -108,39 +179,4 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
final rowsAffected = await query.go(); final rowsAffected = await query.go();
return rowsAffected > 0; return rowsAffected > 0;
} }
/// Checks if a team with the given [teamId] exists in the database.
/// Returns `true` if the team exists, `false` otherwise.
Future<bool> teamExists({required String teamId}) async {
final query = select(teamTable)..where((t) => t.id.equals(teamId));
final result = await query.getSingleOrNull();
return result != null;
}
/// Updates the name of the team with the given [teamId].
Future<void> updateTeamName({
required String teamId,
required String newName,
}) async {
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
TeamTableCompanion(name: Value(newName)),
);
}
/// Retrieves the total count of teams in the database.
Future<int> getTeamCount() async {
final count =
await (selectOnly(teamTable)..addColumns([teamTable.id.count()]))
.map((row) => row.read(teamTable.id.count()))
.getSingle();
return count ?? 0;
}
/// Deletes all teams from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteAllTeams() async {
final query = delete(teamTable);
final rowsAffected = await query.go();
return rowsAffected > 0;
}
} }

View File

@@ -5,6 +5,12 @@ part of 'team_dao.dart';
// ignore_for_file: type=lint // ignore_for_file: type=lint
mixin _$TeamDaoMixin on DatabaseAccessor<AppDatabase> { mixin _$TeamDaoMixin on DatabaseAccessor<AppDatabase> {
$TeamTableTable get teamTable => attachedDatabase.teamTable; $TeamTableTable get teamTable => attachedDatabase.teamTable;
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$GameTableTable get gameTable => attachedDatabase.gameTable;
$GroupTableTable get groupTable => attachedDatabase.groupTable;
$MatchTableTable get matchTable => attachedDatabase.matchTable;
$PlayerMatchTableTable get playerMatchTable =>
attachedDatabase.playerMatchTable;
TeamDaoManager get managers => TeamDaoManager(this); TeamDaoManager get managers => TeamDaoManager(this);
} }
@@ -13,4 +19,17 @@ class TeamDaoManager {
TeamDaoManager(this._db); TeamDaoManager(this._db);
$$TeamTableTableTableManager get teamTable => $$TeamTableTableTableManager get teamTable =>
$$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable); $$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable);
$$PlayerTableTableTableManager get playerTable =>
$$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable);
$$GameTableTableTableManager get gameTable =>
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
$$GroupTableTableTableManager get groupTable =>
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
$$MatchTableTableTableManager get matchTable =>
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
$$PlayerMatchTableTableTableManager get playerMatchTable =>
$$PlayerMatchTableTableTableManager(
_db.attachedDatabase,
_db.playerMatchTable,
);
} }

View File

@@ -1190,9 +1190,9 @@ class $MatchTableTable extends MatchTable
late final GeneratedColumn<String> notes = GeneratedColumn<String>( late final GeneratedColumn<String> notes = GeneratedColumn<String>(
'notes', 'notes',
aliasedName, aliasedName,
true, false,
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: true,
); );
static const VerificationMeta _createdAtMeta = const VerificationMeta( static const VerificationMeta _createdAtMeta = const VerificationMeta(
'createdAt', 'createdAt',
@@ -1270,6 +1270,8 @@ class $MatchTableTable extends MatchTable
_notesMeta, _notesMeta,
notes.isAcceptableOrUnknown(data['notes']!, _notesMeta), notes.isAcceptableOrUnknown(data['notes']!, _notesMeta),
); );
} else if (isInserting) {
context.missing(_notesMeta);
} }
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle( context.handle(
@@ -1313,7 +1315,7 @@ class $MatchTableTable extends MatchTable
notes: attachedDatabase.typeMapping.read( notes: attachedDatabase.typeMapping.read(
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}notes'], data['${effectivePrefix}notes'],
), )!,
createdAt: attachedDatabase.typeMapping.read( createdAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime, DriftSqlType.dateTime,
data['${effectivePrefix}created_at'], data['${effectivePrefix}created_at'],
@@ -1336,7 +1338,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
final String gameId; final String gameId;
final String? groupId; final String? groupId;
final String name; final String name;
final String? notes; final String notes;
final DateTime createdAt; final DateTime createdAt;
final DateTime? endedAt; final DateTime? endedAt;
const MatchTableData({ const MatchTableData({
@@ -1344,7 +1346,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
required this.gameId, required this.gameId,
this.groupId, this.groupId,
required this.name, required this.name,
this.notes, required this.notes,
required this.createdAt, required this.createdAt,
this.endedAt, this.endedAt,
}); });
@@ -1357,9 +1359,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
map['group_id'] = Variable<String>(groupId); map['group_id'] = Variable<String>(groupId);
} }
map['name'] = Variable<String>(name); map['name'] = Variable<String>(name);
if (!nullToAbsent || notes != null) { map['notes'] = Variable<String>(notes);
map['notes'] = Variable<String>(notes);
}
map['created_at'] = Variable<DateTime>(createdAt); map['created_at'] = Variable<DateTime>(createdAt);
if (!nullToAbsent || endedAt != null) { if (!nullToAbsent || endedAt != null) {
map['ended_at'] = Variable<DateTime>(endedAt); map['ended_at'] = Variable<DateTime>(endedAt);
@@ -1375,9 +1375,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
? const Value.absent() ? const Value.absent()
: Value(groupId), : Value(groupId),
name: Value(name), name: Value(name),
notes: notes == null && nullToAbsent notes: Value(notes),
? const Value.absent()
: Value(notes),
createdAt: Value(createdAt), createdAt: Value(createdAt),
endedAt: endedAt == null && nullToAbsent endedAt: endedAt == null && nullToAbsent
? const Value.absent() ? const Value.absent()
@@ -1395,7 +1393,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
gameId: serializer.fromJson<String>(json['gameId']), gameId: serializer.fromJson<String>(json['gameId']),
groupId: serializer.fromJson<String?>(json['groupId']), groupId: serializer.fromJson<String?>(json['groupId']),
name: serializer.fromJson<String>(json['name']), name: serializer.fromJson<String>(json['name']),
notes: serializer.fromJson<String?>(json['notes']), notes: serializer.fromJson<String>(json['notes']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']), createdAt: serializer.fromJson<DateTime>(json['createdAt']),
endedAt: serializer.fromJson<DateTime?>(json['endedAt']), endedAt: serializer.fromJson<DateTime?>(json['endedAt']),
); );
@@ -1408,7 +1406,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
'gameId': serializer.toJson<String>(gameId), 'gameId': serializer.toJson<String>(gameId),
'groupId': serializer.toJson<String?>(groupId), 'groupId': serializer.toJson<String?>(groupId),
'name': serializer.toJson<String>(name), 'name': serializer.toJson<String>(name),
'notes': serializer.toJson<String?>(notes), 'notes': serializer.toJson<String>(notes),
'createdAt': serializer.toJson<DateTime>(createdAt), 'createdAt': serializer.toJson<DateTime>(createdAt),
'endedAt': serializer.toJson<DateTime?>(endedAt), 'endedAt': serializer.toJson<DateTime?>(endedAt),
}; };
@@ -1419,7 +1417,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
String? gameId, String? gameId,
Value<String?> groupId = const Value.absent(), Value<String?> groupId = const Value.absent(),
String? name, String? name,
Value<String?> notes = const Value.absent(), String? notes,
DateTime? createdAt, DateTime? createdAt,
Value<DateTime?> endedAt = const Value.absent(), Value<DateTime?> endedAt = const Value.absent(),
}) => MatchTableData( }) => MatchTableData(
@@ -1427,7 +1425,7 @@ class MatchTableData extends DataClass implements Insertable<MatchTableData> {
gameId: gameId ?? this.gameId, gameId: gameId ?? this.gameId,
groupId: groupId.present ? groupId.value : this.groupId, groupId: groupId.present ? groupId.value : this.groupId,
name: name ?? this.name, name: name ?? this.name,
notes: notes.present ? notes.value : this.notes, notes: notes ?? this.notes,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
endedAt: endedAt.present ? endedAt.value : this.endedAt, endedAt: endedAt.present ? endedAt.value : this.endedAt,
); );
@@ -1478,7 +1476,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
final Value<String> gameId; final Value<String> gameId;
final Value<String?> groupId; final Value<String?> groupId;
final Value<String> name; final Value<String> name;
final Value<String?> notes; final Value<String> notes;
final Value<DateTime> createdAt; final Value<DateTime> createdAt;
final Value<DateTime?> endedAt; final Value<DateTime?> endedAt;
final Value<int> rowid; final Value<int> rowid;
@@ -1497,13 +1495,14 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
required String gameId, required String gameId,
this.groupId = const Value.absent(), this.groupId = const Value.absent(),
required String name, required String name,
this.notes = const Value.absent(), required String notes,
required DateTime createdAt, required DateTime createdAt,
this.endedAt = const Value.absent(), this.endedAt = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
gameId = Value(gameId), gameId = Value(gameId),
name = Value(name), name = Value(name),
notes = Value(notes),
createdAt = Value(createdAt); createdAt = Value(createdAt);
static Insertable<MatchTableData> custom({ static Insertable<MatchTableData> custom({
Expression<String>? id, Expression<String>? id,
@@ -1532,7 +1531,7 @@ class MatchTableCompanion extends UpdateCompanion<MatchTableData> {
Value<String>? gameId, Value<String>? gameId,
Value<String?>? groupId, Value<String?>? groupId,
Value<String>? name, Value<String>? name,
Value<String?>? notes, Value<String>? notes,
Value<DateTime>? createdAt, Value<DateTime>? createdAt,
Value<DateTime?>? endedAt, Value<DateTime?>? endedAt,
Value<int>? rowid, Value<int>? rowid,
@@ -2122,7 +2121,7 @@ class $PlayerMatchTableTable extends PlayerMatchTable
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways( defaultConstraints: GeneratedColumn.constraintIsAlways(
'REFERENCES team_table (id)', 'REFERENCES team_table (id) ON DELETE SET NULL',
), ),
); );
@override @override
@@ -2820,6 +2819,13 @@ abstract class _$AppDatabase extends GeneratedDatabase {
), ),
result: [TableUpdate('player_match_table', kind: UpdateKind.delete)], result: [TableUpdate('player_match_table', kind: UpdateKind.delete)],
), ),
WritePropagation(
on: TableUpdateQuery.onTableName(
'team_table',
limitUpdateKind: UpdateKind.delete,
),
result: [TableUpdate('player_match_table', kind: UpdateKind.update)],
),
WritePropagation( WritePropagation(
on: TableUpdateQuery.onTableName( on: TableUpdateQuery.onTableName(
'player_table', 'player_table',
@@ -4086,7 +4092,7 @@ typedef $$MatchTableTableCreateCompanionBuilder =
required String gameId, required String gameId,
Value<String?> groupId, Value<String?> groupId,
required String name, required String name,
Value<String?> notes, required String notes,
required DateTime createdAt, required DateTime createdAt,
Value<DateTime?> endedAt, Value<DateTime?> endedAt,
Value<int> rowid, Value<int> rowid,
@@ -4097,7 +4103,7 @@ typedef $$MatchTableTableUpdateCompanionBuilder =
Value<String> gameId, Value<String> gameId,
Value<String?> groupId, Value<String?> groupId,
Value<String> name, Value<String> name,
Value<String?> notes, Value<String> notes,
Value<DateTime> createdAt, Value<DateTime> createdAt,
Value<DateTime?> endedAt, Value<DateTime?> endedAt,
Value<int> rowid, Value<int> rowid,
@@ -4560,7 +4566,7 @@ class $$MatchTableTableTableManager
Value<String> gameId = const Value.absent(), Value<String> gameId = const Value.absent(),
Value<String?> groupId = const Value.absent(), Value<String?> groupId = const Value.absent(),
Value<String> name = const Value.absent(), Value<String> name = const Value.absent(),
Value<String?> notes = const Value.absent(), Value<String> notes = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(), Value<DateTime> createdAt = const Value.absent(),
Value<DateTime?> endedAt = const Value.absent(), Value<DateTime?> endedAt = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
@@ -4580,7 +4586,7 @@ class $$MatchTableTableTableManager
required String gameId, required String gameId,
Value<String?> groupId = const Value.absent(), Value<String?> groupId = const Value.absent(),
required String name, required String name,
Value<String?> notes = const Value.absent(), required String notes,
required DateTime createdAt, required DateTime createdAt,
Value<DateTime?> endedAt = const Value.absent(), Value<DateTime?> endedAt = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),

View File

@@ -12,7 +12,7 @@ class MatchTable extends Table {
.references(GroupTable, #id, onDelete: KeyAction.setNull) .references(GroupTable, #id, onDelete: KeyAction.setNull)
.nullable()(); .nullable()();
TextColumn get name => text()(); TextColumn get name => text()();
TextColumn get notes => text().nullable()(); TextColumn get notes => text()();
DateTimeColumn get createdAt => dateTime()(); DateTimeColumn get createdAt => dateTime()();
DateTimeColumn get endedAt => dateTime().nullable()(); DateTimeColumn get endedAt => dateTime().nullable()();

View File

@@ -8,7 +8,9 @@ class PlayerMatchTable extends Table {
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
TextColumn get matchId => TextColumn get matchId =>
text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
TextColumn get teamId => text().references(TeamTable, #id).nullable()(); TextColumn get teamId => text()
.references(TeamTable, #id, onDelete: KeyAction.setNull)
.nullable()();
@override @override
Set<Column<Object>> get primaryKey => {playerId, matchId}; Set<Column<Object>> get primaryKey => {playerId, matchId};

View File

@@ -27,7 +27,43 @@ class Game {
return 'Game{id: $id, name: $name, ruleset: $ruleset, description: $description, color: $color, icon: $icon}'; return 'Game{id: $id, name: $name, ruleset: $ruleset, description: $description, color: $color, icon: $icon}';
} }
/// Creates a Game instance from a JSON object. Game copyWith({
String? id,
DateTime? createdAt,
String? name,
Ruleset? ruleset,
String? description,
GameColor? color,
String? icon,
}) {
return Game(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
name: name ?? this.name,
ruleset: ruleset ?? this.ruleset,
description: description ?? this.description,
color: color ?? this.color,
icon: icon ?? this.icon,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Game &&
runtimeType == other.runtimeType &&
id == other.id &&
createdAt == other.createdAt &&
name == other.name &&
ruleset == other.ruleset &&
description == other.description &&
color == other.color &&
icon == other.icon;
@override
int get hashCode =>
Object.hash(id, createdAt, name, ruleset, description, color, icon);
Game.fromJson(Map<String, dynamic> json) Game.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
@@ -40,7 +76,6 @@ class Game {
color = GameColor.values.firstWhere((e) => e.name == json['color']), color = GameColor.values.firstWhere((e) => e.name == json['color']),
icon = json['icon']; icon = json['icon'];
/// Converts the Game instance to a JSON object.
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),

View File

@@ -1,4 +1,5 @@
import 'package:clock/clock.dart'; import 'package:clock/clock.dart';
import 'package:collection/collection.dart';
import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/player.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@@ -24,6 +25,42 @@ class Group {
return 'Group{id: $id, name: $name, description: $description, members: $members}'; return 'Group{id: $id, name: $name, description: $description, members: $members}';
} }
Group copyWith({
String? id,
String? name,
String? description,
DateTime? createdAt,
List<Player>? members,
}) {
return Group(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
createdAt: createdAt ?? this.createdAt,
members: members ?? this.members,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Group &&
runtimeType == other.runtimeType &&
id == other.id &&
name == other.name &&
description == other.description &&
createdAt == other.createdAt &&
const DeepCollectionEquality().equals(members, other.members);
@override
int get hashCode => Object.hash(
id,
name,
description,
createdAt,
const DeepCollectionEquality().hash(members),
);
/// Creates a Group instance from a JSON object where the related [Player] /// Creates a Group instance from a JSON object where the related [Player]
/// objects are represented by their IDs. /// objects are represented by their IDs.
Group.fromJson(Map<String, dynamic> json) Group.fromJson(Map<String, dynamic> json)

View File

@@ -1,9 +1,11 @@
import 'package:clock/clock.dart'; import 'package:clock/clock.dart';
import 'package:collection/collection.dart';
import 'package:tallee/core/enums.dart'; import 'package:tallee/core/enums.dart';
import 'package:tallee/data/models/game.dart'; import 'package:tallee/data/models/game.dart';
import 'package:tallee/data/models/group.dart'; import 'package:tallee/data/models/group.dart';
import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/score_entry.dart'; import 'package:tallee/data/models/score_entry.dart';
import 'package:tallee/data/models/team.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class Match { class Match {
@@ -14,6 +16,7 @@ class Match {
final Game game; final Game game;
final Group? group; final Group? group;
final List<Player> players; final List<Player> players;
final List<Team>? teams;
final String notes; final String notes;
Map<String, ScoreEntry?> scores; Map<String, ScoreEntry?> scores;
@@ -23,6 +26,7 @@ class Match {
required this.players, required this.players,
this.endedAt, this.endedAt,
this.group, this.group,
this.teams,
this.notes = '', this.notes = '',
String? id, String? id,
DateTime? createdAt, DateTime? createdAt,
@@ -36,9 +40,62 @@ class Match {
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, mvp: $mvp}'; return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, mvp: $mvp}';
} }
/// Creates a Match instance from a JSON object where related objects are Match copyWith({
/// represented by their IDs. Therefore, the game, group, and players are not String? id,
/// fully constructed here. DateTime? createdAt,
DateTime? endedAt,
String? name,
Game? game,
Group? group,
List<Player>? players,
List<Team>? teams,
String? notes,
Map<String, ScoreEntry?>? scores,
}) {
return Match(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
endedAt: endedAt ?? this.endedAt,
name: name ?? this.name,
game: game ?? this.game,
group: group ?? this.group,
players: players ?? this.players,
teams: teams ?? this.teams,
notes: notes ?? this.notes,
scores: scores ?? this.scores,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Match &&
runtimeType == other.runtimeType &&
id == other.id &&
createdAt == other.createdAt &&
endedAt == other.endedAt &&
name == other.name &&
game == other.game &&
group == other.group &&
const DeepCollectionEquality().equals(players, other.players) &&
const DeepCollectionEquality().equals(teams, other.teams) &&
notes == other.notes &&
const DeepCollectionEquality().equals(scores, other.scores);
@override
int get hashCode => Object.hash(
id,
createdAt,
endedAt,
name,
game,
group,
const DeepCollectionEquality().hash(players),
const DeepCollectionEquality().hash(teams),
notes,
const DeepCollectionEquality().hash(scores),
);
Match.fromJson(Map<String, dynamic> json) Match.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
@@ -55,6 +112,7 @@ class Match {
), ),
group = null, group = null,
players = [], players = [],
teams = [],
scores = json['scores'] != null scores = json['scores'] != null
? (json['scores'] as Map<String, dynamic>).map( ? (json['scores'] as Map<String, dynamic>).map(
(key, value) => MapEntry( (key, value) => MapEntry(
@@ -67,9 +125,6 @@ class Match {
: {}, : {},
notes = json['notes'] ?? ''; notes = json['notes'] ?? '';
/// Converts the Match instance to a JSON object. Related objects are
/// represented by their IDs, so the game, group, and players are not fully
/// serialized here.
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),
@@ -78,6 +133,7 @@ class Match {
'gameId': game.id, 'gameId': game.id,
'groupId': group?.id, 'groupId': group?.id,
'playerIds': players.map((player) => player.id).toList(), 'playerIds': players.map((player) => player.id).toList(),
'teams': teams?.map((team) => team.toJson()).toList(),
'scores': scores.map((key, value) => MapEntry(key, value?.toJson())), 'scores': scores.map((key, value) => MapEntry(key, value?.toJson())),
'notes': notes, 'notes': notes,
}; };

View File

@@ -23,7 +23,36 @@ class Player {
return 'Player{id: $id, createdAt: $createdAt, name: $name, nameCount: $nameCount, description: $description}'; return 'Player{id: $id, createdAt: $createdAt, name: $name, nameCount: $nameCount, description: $description}';
} }
/// Creates a Player instance from a JSON object. Player copyWith({
String? id,
DateTime? createdAt,
String? name,
int? nameCount,
String? description,
}) {
return Player(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
name: name ?? this.name,
nameCount: nameCount ?? this.nameCount,
description: description ?? this.description,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Player &&
runtimeType == other.runtimeType &&
id == other.id &&
createdAt == other.createdAt &&
name == other.name &&
nameCount == other.nameCount &&
description == other.description;
@override
int get hashCode => Object.hash(id, createdAt, name, nameCount, description);
Player.fromJson(Map<String, dynamic> json) Player.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
@@ -31,7 +60,6 @@ class Player {
nameCount = 0, nameCount = 0,
description = json['description']; description = json['description'];
/// Converts the Player instance to a JSON object.
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),

View File

@@ -10,6 +10,26 @@ class ScoreEntry {
return 'ScoreEntry{roundNumber: $roundNumber, score: $score, change: $change}'; return 'ScoreEntry{roundNumber: $roundNumber, score: $score, change: $change}';
} }
ScoreEntry copyWith({int? roundNumber, int? score, int? change}) {
return ScoreEntry(
roundNumber: roundNumber ?? this.roundNumber,
score: score ?? this.score,
change: change ?? this.change,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ScoreEntry &&
runtimeType == other.runtimeType &&
roundNumber == other.roundNumber &&
score == other.score &&
change == other.change;
@override
int get hashCode => Object.hash(roundNumber, score, change);
ScoreEntry.fromJson(Map<String, dynamic> json) ScoreEntry.fromJson(Map<String, dynamic> json)
: roundNumber = json['roundNumber'], : roundNumber = json['roundNumber'],
score = json['score'], score = json['score'],

View File

@@ -1,4 +1,5 @@
import 'package:clock/clock.dart'; import 'package:clock/clock.dart';
import 'package:collection/collection.dart';
import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/player.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@@ -21,16 +22,44 @@ class Team {
return 'Team{id: $id, name: $name, members: $members}'; return 'Team{id: $id, name: $name, members: $members}';
} }
/// Creates a Team instance from a JSON object (memberIds format). Team copyWith({
/// Player objects are reconstructed from memberIds by the DataTransferService. String? id,
String? name,
DateTime? createdAt,
List<Player>? members,
}) {
return Team(
id: id ?? this.id,
name: name ?? this.name,
createdAt: createdAt ?? this.createdAt,
members: members ?? this.members,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Team &&
runtimeType == other.runtimeType &&
id == other.id &&
name == other.name &&
createdAt == other.createdAt &&
const DeepCollectionEquality().equals(members, other.members);
@override
int get hashCode => Object.hash(
id,
name,
createdAt,
const DeepCollectionEquality().hash(members),
);
Team.fromJson(Map<String, dynamic> json) Team.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
name = json['name'], name = json['name'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
members = []; // Populated during import via DataTransferService members = []; // Populated during import via DataTransferService
/// Converts the Team instance to a JSON object. Related objects are
/// represented by their IDs.
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'name': name, 'name': name,

View File

@@ -172,12 +172,12 @@ class _CreateGroupViewState extends State<CreateGroupView> {
if (widget.groupToEdit!.name != groupName) { if (widget.groupToEdit!.name != groupName) {
successfullNameChange = await db.groupDao.updateGroupName( successfullNameChange = await db.groupDao.updateGroupName(
groupId: widget.groupToEdit!.id, groupId: widget.groupToEdit!.id,
newName: groupName, name: groupName,
); );
} }
if (widget.groupToEdit!.members != selectedPlayers) { if (widget.groupToEdit!.members != selectedPlayers) {
successfullMemberChange = await db.groupDao.replaceGroupPlayers( successfullMemberChange = await db.playerGroupDao.replaceGroupPlayers(
groupId: widget.groupToEdit!.id, groupId: widget.groupToEdit!.id,
newPlayers: selectedPlayers, newPlayers: selectedPlayers,
); );

View File

@@ -284,14 +284,14 @@ class _CreateMatchViewState extends State<CreateMatchView> {
if (widget.matchToEdit!.name != updatedMatch.name) { if (widget.matchToEdit!.name != updatedMatch.name) {
await db.matchDao.updateMatchName( await db.matchDao.updateMatchName(
matchId: widget.matchToEdit!.id, matchId: widget.matchToEdit!.id,
newName: updatedMatch.name, name: updatedMatch.name,
); );
} }
if (widget.matchToEdit!.group?.id != updatedMatch.group?.id) { if (widget.matchToEdit!.group?.id != updatedMatch.group?.id) {
await db.matchDao.updateMatchGroup( await db.matchDao.updateMatchGroup(
matchId: widget.matchToEdit!.id, matchId: widget.matchToEdit!.id,
newGroupId: updatedMatch.group?.id, groupId: updatedMatch.group?.id,
); );
} }

View File

@@ -242,9 +242,9 @@ class _MatchResultViewState extends State<MatchResultView> {
/// Handles saving or removing the loser in the database. /// Handles saving or removing the loser in the database.
Future<bool> _handleLoser() async { Future<bool> _handleLoser() async {
if (_selectedPlayer == null) { if (_selectedPlayer == null) {
return await db.scoreEntryDao.removeLooser(matchId: widget.match.id); return await db.scoreEntryDao.removeLoser(matchId: widget.match.id);
} else { } else {
return await db.scoreEntryDao.setLooser( return await db.scoreEntryDao.setLoser(
matchId: widget.match.id, matchId: widget.match.id,
playerId: _selectedPlayer!.id, playerId: _selectedPlayer!.id,
); );

View File

@@ -160,6 +160,7 @@ const allDependencies = <Package>[
/// Direct `dependencies`. /// Direct `dependencies`.
const dependencies = <Package>[ const dependencies = <Package>[
_clock, _clock,
_collection,
_cupertino_icons, _cupertino_icons,
_drift, _drift,
_drift_flutter, _drift_flutter,
@@ -444,13 +445,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
); );
/// build 4.0.5 /// build 4.0.6
const _build = Package( const _build = Package(
name: 'build', name: 'build',
description: 'A package for authoring build_runner compatible code generators.', description: 'A package for authoring build_runner compatible code generators.',
repository: 'https://github.com/dart-lang/build/tree/master/build', repository: 'https://github.com/dart-lang/build/tree/master/build',
authors: [], authors: [],
version: '4.0.5', version: '4.0.6',
spdxIdentifiers: ['BSD-3-Clause'], spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
@@ -567,13 +568,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
); );
/// build_runner 2.14.0 /// build_runner 2.15.0
const _build_runner = Package( const _build_runner = Package(
name: 'build_runner', name: 'build_runner',
description: 'A build system for Dart code generation and modular compilation.', description: 'A build system for Dart code generation and modular compilation.',
repository: 'https://github.com/dart-lang/build/tree/master/build_runner', repository: 'https://github.com/dart-lang/build/tree/master/build_runner',
authors: [], authors: [],
version: '2.14.0', version: '2.15.0',
spdxIdentifiers: ['BSD-3-Clause'], spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
@@ -651,14 +652,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
); );
/// built_value 8.12.5 /// built_value 8.12.6
const _built_value = Package( const _built_value = Package(
name: 'built_value', name: 'built_value',
description: '''Value types with builders, Dart classes as enums, and serialization. This library is the runtime dependency. description: '''Value types with builders, Dart classes as enums, and serialization. This library is the runtime dependency.
''', ''',
repository: 'https://github.com/google/built_value.dart/tree/master/built_value', repository: 'https://github.com/google/built_value.dart/tree/master/built_value',
authors: [], authors: [],
version: '8.12.5', version: '8.12.6',
spdxIdentifiers: ['BSD-3-Clause'], spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
@@ -36204,13 +36205,13 @@ Copyright (C) 2009-2017, International Business Machines Corporation,
Google, and others. All Rights Reserved.''', Google, and others. All Rights Reserved.''',
); );
/// source_gen 4.2.2 /// source_gen 4.2.3
const _source_gen = Package( const _source_gen = Package(
name: 'source_gen', name: 'source_gen',
description: 'Source code generation builders and utilities for the Dart build system', description: 'Source code generation builders and utilities for the Dart build system',
repository: 'https://github.com/dart-lang/source_gen/tree/master/source_gen', repository: 'https://github.com/dart-lang/source_gen/tree/master/source_gen',
authors: [], authors: [],
version: '4.2.2', version: '4.2.3',
spdxIdentifiers: ['BSD-3-Clause'], spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
@@ -37481,13 +37482,13 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution.''', 3. This notice may not be removed or altered from any source distribution.''',
); );
/// vm_service 15.1.0 /// vm_service 15.2.0
const _vm_service = Package( const _vm_service = Package(
name: 'vm_service', name: 'vm_service',
description: 'A library to communicate with a service implementing the Dart VM service protocol.', description: 'A library to communicate with a service implementing the Dart VM service protocol.',
repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/vm_service', repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/vm_service',
authors: [], authors: [],
version: '15.1.0', version: '15.2.0',
spdxIdentifiers: ['BSD-3-Clause'], spdxIdentifiers: ['BSD-3-Clause'],
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
@@ -37881,16 +37882,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.''', SOFTWARE.''',
); );
/// tallee 0.0.23+257 /// tallee 0.0.24+258
const _tallee = Package( const _tallee = Package(
name: 'tallee', name: 'tallee',
description: 'Tracking App for Card Games', description: 'Tracking App for Card Games',
authors: [], authors: [],
version: '0.0.23+257', version: '0.0.24+258',
spdxIdentifiers: ['LGPL-3.0'], spdxIdentifiers: ['LGPL-3.0'],
isMarkdown: false, isMarkdown: false,
isSdk: false, isSdk: false,
dependencies: [PackageRef('clock'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('flutter'), PackageRef('flutter_localizations'), PackageRef('fluttericon'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')], dependencies: [PackageRef('clock'), PackageRef('collection'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('flutter'), PackageRef('flutter_localizations'), PackageRef('fluttericon'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')],
devDependencies: [PackageRef('flutter_test'), PackageRef('build_runner'), PackageRef('dart_pubspec_licenses'), PackageRef('drift_dev'), PackageRef('flutter_lints')], devDependencies: [PackageRef('flutter_test'), PackageRef('build_runner'), PackageRef('dart_pubspec_licenses'), PackageRef('drift_dev'), PackageRef('flutter_lints')],
license: '''GNU LESSER GENERAL PUBLIC LICENSE license: '''GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007

View File

@@ -35,13 +35,11 @@ class DataTransferService {
final groups = await db.groupDao.getAllGroups(); final groups = await db.groupDao.getAllGroups();
final players = await db.playerDao.getAllPlayers(); final players = await db.playerDao.getAllPlayers();
final games = await db.gameDao.getAllGames(); final games = await db.gameDao.getAllGames();
final teams = await db.teamDao.getAllTeams();
final Map<String, dynamic> jsonMap = { final Map<String, dynamic> jsonMap = {
'players': players.map((player) => player.toJson()).toList(), 'players': players.map((player) => player.toJson()).toList(),
'games': games.map((game) => game.toJson()).toList(), 'games': games.map((game) => game.toJson()).toList(),
'groups': groups.map((group) => group.toJson()).toList(), 'groups': groups.map((group) => group.toJson()).toList(),
'teams': teams.map((team) => team.toJson()).toList(),
'matches': matches.map((match) => match.toJson()).toList(), 'matches': matches.map((match) => match.toJson()).toList(),
}; };
@@ -130,8 +128,6 @@ class DataTransferService {
final importedGroups = parseGroupsFromJson(decodedJson, playerById); final importedGroups = parseGroupsFromJson(decodedJson, playerById);
final groupById = {for (final g in importedGroups) g.id: g}; final groupById = {for (final g in importedGroups) g.id: g};
final importedTeams = parseTeamsFromJson(decodedJson, playerById);
final importedMatches = parseMatchesFromJson( final importedMatches = parseMatchesFromJson(
decodedJson, decodedJson,
gameById, gameById,
@@ -142,8 +138,7 @@ class DataTransferService {
await db.playerDao.addPlayersAsList(players: importedPlayers); await db.playerDao.addPlayersAsList(players: importedPlayers);
await db.gameDao.addGamesAsList(games: importedGames); await db.gameDao.addGamesAsList(games: importedGames);
await db.groupDao.addGroupsAsList(groups: importedGroups); await db.groupDao.addGroupsAsList(groups: importedGroups);
await db.teamDao.addTeamsAsList(teams: importedTeams); await db.matchDao.addMatchesAsList(matches: importedMatches);
await db.matchDao.addMatchAsList(matches: importedMatches);
} }
/* Parsing Methods */ /* Parsing Methods */
@@ -190,13 +185,12 @@ class DataTransferService {
}).toList(); }).toList();
} }
/// Parses teams from JSON data. /// Parses teams from a list of JSON objects.
@visibleForTesting @visibleForTesting
static List<Team> parseTeamsFromJson( static List<Team> parseTeamsFromJson(
Map<String, dynamic> decodedJson, List<dynamic> teamsJson,
Map<String, Player> playerById, Map<String, Player> playerById,
) { ) {
final teamsJson = (decodedJson['teams'] as List<dynamic>?) ?? [];
return teamsJson.map((t) { return teamsJson.map((t) {
final map = t as Map<String, dynamic>; final map = t as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? []) final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
@@ -259,12 +253,16 @@ class DataTransferService {
.whereType<Player>() .whereType<Player>()
.toList(); .toList();
final teamsJson = (map['teams'] as List<dynamic>?) ?? [];
final teams = parseTeamsFromJson(teamsJson, playersMap);
return Match( return Match(
id: id, id: id,
name: name, name: name,
game: game, game: game,
group: group, group: group,
players: players, players: players,
teams: teams.isEmpty ? null : teams,
createdAt: createdAt, createdAt: createdAt,
endedAt: endedAt, endedAt: endedAt,
notes: notes, notes: notes,

View File

@@ -1,13 +1,14 @@
name: tallee name: tallee
description: "Tracking App for Card Games" description: "Tracking App for Card Games"
publish_to: 'none' publish_to: 'none'
version: 0.0.23+257 version: 0.0.24+258
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1
dependencies: dependencies:
clock: ^1.1.2 clock: ^1.1.2
collection: ^1.19.1
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
drift: ^2.27.0 drift: ^2.27.0
drift_flutter: ^0.2.4 drift_flutter: ^0.2.4

View File

@@ -35,25 +35,23 @@ void main() {
testPlayer4 = Player(name: 'Diana'); testPlayer4 = Player(name: 'Diana');
testGroup1 = Group( testGroup1 = Group(
name: 'Test Group', name: 'Test Group',
description: '',
members: [testPlayer1, testPlayer2, testPlayer3], members: [testPlayer1, testPlayer2, testPlayer3],
description: 'description of the test group 1',
); );
testGroup2 = Group( testGroup2 = Group(
id: 'gr2', id: 'gr2',
name: 'Second Group', name: 'Second Group',
description: '',
members: [testPlayer2, testPlayer3, testPlayer4], members: [testPlayer2, testPlayer3, testPlayer4],
description: 'description of the test group 2',
); );
testGroup3 = Group( testGroup3 = Group(
id: 'gr2', id: 'gr2',
name: 'Second Group', name: 'Second Group',
description: '',
members: [testPlayer2, testPlayer4], members: [testPlayer2, testPlayer4],
); );
testGroup4 = Group( testGroup4 = Group(
id: 'gr2', id: 'gr2',
name: 'Second Group', name: 'Second Group',
description: '',
members: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], members: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
); );
}); });
@@ -62,312 +60,355 @@ void main() {
await database.close(); await database.close();
}); });
group('Group Tests', () { group('Group Tests', () {
// Verifies that a single group can be added and retrieved with all fields and members intact. group('CREATE', () {
test('Adding and fetching a single group works correctly', () async { test('Adding and fetching a single group works correctly', () async {
await database.groupDao.addGroup(group: testGroup1); await database.groupDao.addGroup(group: testGroup1);
final fetchedGroup = await database.groupDao.getGroupById( final fetchedGroup = await database.groupDao.getGroupById(
groupId: testGroup1.id, groupId: testGroup1.id,
);
expect(fetchedGroup.id, testGroup1.id);
expect(fetchedGroup.name, testGroup1.name);
expect(fetchedGroup.createdAt, testGroup1.createdAt);
expect(fetchedGroup.members.length, testGroup1.members.length);
for (int i = 0; i < testGroup1.members.length; i++) {
expect(fetchedGroup.members[i].id, testGroup1.members[i].id);
expect(fetchedGroup.members[i].name, testGroup1.members[i].name);
expect(
fetchedGroup.members[i].createdAt,
testGroup1.members[i].createdAt,
); );
}
});
// Verifies that multiple groups can be added and retrieved with correct members. expect(fetchedGroup.id, testGroup1.id);
test('Adding and fetching multiple groups works correctly', () async { expect(fetchedGroup.name, testGroup1.name);
await database.groupDao.addGroupsAsList( expect(fetchedGroup.createdAt, testGroup1.createdAt);
groups: [testGroup1, testGroup2, testGroup3, testGroup4],
);
final allGroups = await database.groupDao.getAllGroups(); expect(fetchedGroup.members.length, testGroup1.members.length);
expect(allGroups.length, 2); for (int i = 0; i < testGroup1.members.length; i++) {
expect(fetchedGroup.members[i].id, testGroup1.members[i].id);
final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2}; expect(fetchedGroup.members[i].name, testGroup1.members[i].name);
expect(
for (final group in allGroups) { fetchedGroup.members[i].createdAt,
final testGroup = testGroups[group.id]!; testGroup1.members[i].createdAt,
);
expect(group.id, testGroup.id);
expect(group.name, testGroup.name);
expect(group.createdAt, testGroup.createdAt);
expect(group.members.length, testGroup.members.length);
for (int i = 0; i < testGroup.members.length; i++) {
expect(group.members[i].id, testGroup.members[i].id);
expect(group.members[i].name, testGroup.members[i].name);
expect(group.members[i].createdAt, testGroup.members[i].createdAt);
} }
} });
});
// Verifies that adding the same group twice does not create duplicates. test('Adding the same group twice does not create duplicates', () async {
test('Adding the same group twice does not create duplicates', () async { await database.groupDao.addGroup(group: testGroup1);
await database.groupDao.addGroup(group: testGroup1); await database.groupDao.addGroup(group: testGroup1);
await database.groupDao.addGroup(group: testGroup1);
final allGroups = await database.groupDao.getAllGroups(); final allGroups = await database.groupDao.getAllGroups();
expect(allGroups.length, 1); expect(allGroups.length, 1);
});
// Verifies that groupExists returns correct boolean based on group presence. final fetchedGroup = await database.groupDao.getGroupById(
test('Group existence check works correctly', () async { groupId: testGroup1.id,
var groupExists = await database.groupDao.groupExists(
groupId: testGroup1.id,
);
expect(groupExists, false);
await database.groupDao.addGroup(group: testGroup1);
groupExists = await database.groupDao.groupExists(groupId: testGroup1.id);
expect(groupExists, true);
});
// Verifies that deleteGroup removes the group and returns true.
test('Deleting a group works correctly', () async {
await database.groupDao.addGroup(group: testGroup1);
final groupDeleted = await database.groupDao.deleteGroup(
groupId: testGroup1.id,
);
expect(groupDeleted, true);
final groupExists = await database.groupDao.groupExists(
groupId: testGroup1.id,
);
expect(groupExists, false);
});
// Verifies that updateGroupName correctly updates only the name field.
test('Updating a group name works correctly', () async {
await database.groupDao.addGroup(group: testGroup1);
const newGroupName = 'new group name';
await database.groupDao.updateGroupName(
groupId: testGroup1.id,
newName: newGroupName,
);
final result = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(result.name, newGroupName);
});
// Verifies that getGroupCount returns correct count through add/delete operations.
test('Getting the group count works correctly', () async {
final initialCount = await database.groupDao.getGroupCount();
expect(initialCount, 0);
await database.groupDao.addGroup(group: testGroup1);
final groupAdded = await database.groupDao.getGroupCount();
expect(groupAdded, 1);
final groupRemoved = await database.groupDao.deleteGroup(
groupId: testGroup1.id,
);
expect(groupRemoved, true);
final finalCount = await database.groupDao.getGroupCount();
expect(finalCount, 0);
});
// Verifies that getAllGroups returns an empty list when no groups exist.
test('getAllGroups returns empty list when no groups exist', () async {
final allGroups = await database.groupDao.getAllGroups();
expect(allGroups, isEmpty);
});
// Verifies that getGroupById throws StateError for non-existent group ID.
test('getGroupById throws exception for non-existent group', () async {
expect(
() => database.groupDao.getGroupById(groupId: 'non-existent-id'),
throwsA(isA<StateError>()),
);
});
// Verifies that addGroup returns false when trying to add a duplicate group.
test('addGroup returns false when group already exists', () async {
final firstAdd = await database.groupDao.addGroup(group: testGroup1);
expect(firstAdd, true);
final secondAdd = await database.groupDao.addGroup(group: testGroup1);
expect(secondAdd, false);
final allGroups = await database.groupDao.getAllGroups();
expect(allGroups.length, 1);
});
// Verifies that addGroupsAsList handles an empty list without errors.
test('addGroupsAsList handles empty list correctly', () async {
await database.groupDao.addGroupsAsList(groups: []);
final allGroups = await database.groupDao.getAllGroups();
expect(allGroups.length, 0);
});
// Verifies that deleteGroup returns false for a non-existent group ID.
test('deleteGroup returns false for non-existent group', () async {
final deleted = await database.groupDao.deleteGroup(
groupId: 'non-existent-id',
);
expect(deleted, false);
});
// Verifies that updateGroupName returns false for a non-existent group ID.
test('updateGroupName returns false for non-existent group', () async {
final updated = await database.groupDao.updateGroupName(
groupId: 'non-existent-id',
newName: 'New Name',
);
expect(updated, false);
});
// Verifies that updateGroupDescription correctly updates the description field.
test('Updating a group description works correctly', () async {
await database.groupDao.addGroup(group: testGroup1);
const newDescription = 'This is a new description';
final updated = await database.groupDao.updateGroupDescription(
groupId: testGroup1.id,
newDescription: newDescription,
);
expect(updated, true);
final result = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(result.description, newDescription);
});
// Verifies that updateGroupDescription can set the description to null.
test('updateGroupDescription can set description to null', () async {
final groupWithDescription = Group(
name: 'Group with description',
description: 'Initial description',
members: [testPlayer1],
);
await database.groupDao.addGroup(group: groupWithDescription);
final updated = await database.groupDao.updateGroupDescription(
groupId: groupWithDescription.id,
newDescription: 'Updated description',
);
expect(updated, true);
final result = await database.groupDao.getGroupById(
groupId: groupWithDescription.id,
);
expect(result.description, 'Updated description');
});
// Verifies that updateGroupDescription returns false for a non-existent group.
test(
'updateGroupDescription returns false for non-existent group',
() async {
final updated = await database.groupDao.updateGroupDescription(
groupId: 'non-existent-id',
newDescription: 'New Description',
); );
expect(updated, false); expect(fetchedGroup.id, testGroup1.id);
}, expect(fetchedGroup.members.length, testGroup1.members.length);
); });
// Verifies that deleteAllGroups removes all groups from the database. test('addGroup() returns false when group already exists', () async {
test('deleteAllGroups removes all groups', () async { final firstAdd = await database.groupDao.addGroup(group: testGroup1);
await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); expect(firstAdd, isTrue);
final countBefore = await database.groupDao.getGroupCount(); final secondAdd = await database.groupDao.addGroup(group: testGroup1);
expect(countBefore, 2); expect(secondAdd, isFalse);
final deleted = await database.groupDao.deleteAllGroups(); final allGroups = await database.groupDao.getAllGroups();
expect(deleted, true); expect(allGroups.length, 1);
});
final countAfter = await database.groupDao.getGroupCount(); test('Adding and fetching multiple groups works correctly', () async {
expect(countAfter, 0); await database.groupDao.addGroupsAsList(
groups: [testGroup1, testGroup2, testGroup3, testGroup4],
);
final allGroups = await database.groupDao.getAllGroups();
expect(allGroups.length, 2);
final testGroups = {
testGroup1.id: testGroup1,
testGroup2.id: testGroup2,
};
for (final group in allGroups) {
final testGroup = testGroups[group.id]!;
expect(group.id, testGroup.id);
expect(group.name, testGroup.name);
expect(group.createdAt, testGroup.createdAt);
expect(group.members.length, testGroup.members.length);
for (int i = 0; i < testGroup.members.length; i++) {
expect(group.members[i].id, testGroup.members[i].id);
expect(group.members[i].name, testGroup.members[i].name);
expect(group.members[i].createdAt, testGroup.members[i].createdAt);
}
}
});
test('addGroupsAsList() handles empty list correctly', () async {
await database.groupDao.addGroupsAsList(groups: []);
final allGroups = await database.groupDao.getAllGroups();
expect(allGroups.length, 0);
});
}); });
// Verifies that deleteAllGroups returns false when no groups exist. group('READ', () {
test('deleteAllGroups returns false when no groups exist', () async { test('groupExists() works correctly', () async {
final deleted = await database.groupDao.deleteAllGroups(); var groupExists = await database.groupDao.groupExists(
expect(deleted, false); groupId: testGroup1.id,
);
expect(groupExists, isFalse);
await database.groupDao.addGroup(group: testGroup1);
groupExists = await database.groupDao.groupExists(
groupId: testGroup1.id,
);
expect(groupExists, isTrue);
});
test('getGroupCount() works correctly', () async {
var count = await database.groupDao.getGroupCount();
expect(count, 0);
var added = await database.groupDao.addGroup(group: testGroup1);
expect(added, isTrue);
count = await database.groupDao.getGroupCount();
expect(count, 1);
added = await database.groupDao.addGroup(group: testGroup2);
expect(added, isTrue);
count = await database.groupDao.getGroupCount();
expect(count, 2);
final removed = await database.groupDao.deleteGroup(
groupId: testGroup1.id,
);
expect(removed, isTrue);
count = await database.groupDao.getGroupCount();
expect(count, 1);
});
test('getGroupById() throws exception for non-existent group', () async {
expect(
() => database.groupDao.getGroupById(groupId: 'non-existent-id'),
throwsA(isA<StateError>()),
);
});
test('getAllGroups() returns empty list when no groups exist', () async {
final allGroups = await database.groupDao.getAllGroups();
expect(allGroups, isEmpty);
});
test('addGroupsAsList() with duplicate groups only adds once', () async {
await database.groupDao.addGroupsAsList(
groups: [testGroup1, testGroup1, testGroup1],
);
final allGroups = await database.groupDao.getAllGroups();
expect(allGroups.length, 1);
});
}); });
// Verifies that groups with special characters (quotes, emojis) are stored correctly. group('UPDATE', () {
test('Group with special characters in name is stored correctly', () async { test('updateGroupName() works correctly', () async {
final specialGroup = Group( await database.groupDao.addGroup(group: testGroup1);
name: 'Group\'s & "Special" <Name>',
description: 'Description with émojis 🎮🎲',
members: [testPlayer1],
);
await database.groupDao.addGroup(group: specialGroup);
final fetchedGroup = await database.groupDao.getGroupById( const newName = 'New name';
groupId: specialGroup.id, await database.groupDao.updateGroupName(
groupId: testGroup1.id,
name: newName,
);
final result = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(result.name, newName);
});
test('updateGroupName() returns false for non-existent group', () async {
final updated = await database.groupDao.updateGroupName(
groupId: 'non-existent-id',
name: 'New name',
);
expect(updated, isFalse);
});
test('updateGroupDescription() works correctly', () async {
await database.groupDao.addGroup(group: testGroup1);
const newDescription = 'New description';
final updated = await database.groupDao.updateGroupDescription(
groupId: testGroup1.id,
description: newDescription,
);
expect(updated, isTrue);
final group = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(group.description, newDescription);
});
test(
'updateGroupDescription() returns false for non-existent group',
() async {
final updated = await database.groupDao.updateGroupDescription(
groupId: 'non-existent-id',
description: 'New description',
);
expect(updated, isFalse);
},
);
test('Multiple updates to the same group work correctly', () async {
await database.groupDao.addGroup(group: testGroup1);
const newName = 'New name';
const newDescription = 'New description';
await database.groupDao.updateGroupName(
groupId: testGroup1.id,
name: newName,
);
await database.groupDao.updateGroupDescription(
groupId: testGroup1.id,
description: newDescription,
);
final updatedGroup = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(updatedGroup.name, newName);
expect(updatedGroup.description, newDescription);
});
test('replaceGroupPlayers() works correctly', () async {
await database.groupDao.addGroup(group: testGroup1);
final initialGroup = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(initialGroup.members.length, 3);
expect(
initialGroup.members
.map((p) => p.id)
.toList()
.contains(testPlayer1.id),
isTrue,
);
expect(
initialGroup.members
.map((p) => p.id)
.toList()
.contains(testPlayer2.id),
isTrue,
);
expect(
initialGroup.members
.map((p) => p.id)
.toList()
.contains(testPlayer3.id),
isTrue,
);
final newPlayers = [testPlayer2, testPlayer4];
final replaced = await database.playerGroupDao.replaceGroupPlayers(
groupId: testGroup1.id,
newPlayers: newPlayers,
);
expect(replaced, isTrue);
final updatedGroup = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(updatedGroup.members.length, 2);
final memberIds = updatedGroup.members.map((p) => p.id).toList();
expect(memberIds.contains(testPlayer2.id), isTrue);
expect(memberIds.contains(testPlayer4.id), isTrue);
expect(memberIds.contains(testPlayer1.id), isFalse);
expect(memberIds.contains(testPlayer3.id), isFalse);
});
test('replaceGroupPlayers() ignores empty list ', () async {
await database.groupDao.addGroup(group: testGroup1);
final replaced = await database.playerGroupDao.replaceGroupPlayers(
groupId: testGroup1.id,
newPlayers: [],
);
expect(replaced, isFalse);
final updatedGroup = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(updatedGroup.members.length, testGroup1.members.length);
});
test(
'replaceGroupPlayers() returns false for non-existent group',
() async {
final replaced = await database.playerGroupDao.replaceGroupPlayers(
groupId: 'non-existent-id',
newPlayers: [testPlayer1],
);
expect(replaced, isFalse);
},
); );
expect(fetchedGroup.name, 'Group\'s & "Special" <Name>');
expect(fetchedGroup.description, 'Description with émojis 🎮🎲');
}); });
// Verifies that a group with an empty members list can be stored and retrieved. group('DELETE', () {
test('Group with empty members list is stored correctly', () async { test('deleteGroup() works correctly', () async {
final emptyGroup = Group( await database.groupDao.addGroup(group: testGroup1);
name: 'Empty Group',
description: '',
members: [],
);
await database.groupDao.addGroup(group: emptyGroup);
final fetchedGroup = await database.groupDao.getGroupById( final groupDeleted = await database.groupDao.deleteGroup(
groupId: emptyGroup.id, groupId: testGroup1.id,
); );
expect(fetchedGroup.name, 'Empty Group'); expect(groupDeleted, isTrue);
expect(fetchedGroup.members, isEmpty);
final groupExists = await database.groupDao.groupExists(
groupId: testGroup1.id,
);
expect(groupExists, isFalse);
});
test('deleteGroup() returns false for non-existent group', () async {
final deleted = await database.groupDao.deleteGroup(
groupId: 'non-existent-id',
);
expect(deleted, isFalse);
});
test('deleteAllGroups() works correctly', () async {
await database.groupDao.addGroupsAsList(
groups: [testGroup1, testGroup2],
);
var count = await database.groupDao.getGroupCount();
expect(count, 2);
final deleted = await database.groupDao.deleteAllGroups();
expect(deleted, isTrue);
count = await database.groupDao.getGroupCount();
expect(count, 0);
});
test('deleteAllGroups() returns false when no groups exist', () async {
final deleted = await database.groupDao.deleteAllGroups();
expect(deleted, isFalse);
});
}); });
// Verifies that multiple sequential updates to the same group work correctly. group('Edge Cases', () {
test('Multiple updates to the same group work correctly', () async { test('Group with special characters is stored correctly', () async {
await database.groupDao.addGroup(group: testGroup1); final specialGroup = Group(
name: 'Group\'s & "Special" <Name>',
description: 'Description with émojis 🎮🎲',
members: [testPlayer1],
);
await database.groupDao.addGroup(group: specialGroup);
await database.groupDao.updateGroupName( final fetchedGroup = await database.groupDao.getGroupById(
groupId: testGroup1.id, groupId: specialGroup.id,
newName: 'Updated Name', );
); expect(fetchedGroup.name, 'Group\'s & "Special" <Name>');
await database.groupDao.updateGroupDescription( expect(fetchedGroup.description, 'Description with émojis 🎮🎲');
groupId: testGroup1.id, });
newDescription: 'Updated Description',
);
final updatedGroup = await database.groupDao.getGroupById(
groupId: testGroup1.id,
);
expect(updatedGroup.name, 'Updated Name');
expect(updatedGroup.description, 'Updated Description');
expect(updatedGroup.members.length, testGroup1.members.length);
});
// Verifies that addGroupsAsList with duplicate groups only adds unique ones.
test('addGroupsAsList with duplicate groups only adds once', () async {
await database.groupDao.addGroupsAsList(
groups: [testGroup1, testGroup1, testGroup1],
);
final allGroups = await database.groupDao.getAllGroups();
expect(allGroups.length, 1);
}); });
}); });
} }

View File

@@ -101,212 +101,221 @@ void main() {
}); });
group('Match Tests', () { group('Match Tests', () {
// Verifies that a single match can be added and retrieved with all fields, group, and players intact. group('CREATE', () {
test('Adding and fetching single match works correctly', () async { test('Adding and fetching single match works correctly', () async {
await database.matchDao.addMatch(match: testMatch1); await database.matchDao.addMatch(match: testMatch1);
final result = await database.matchDao.getMatchById( final result = await database.matchDao.getMatchById(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(result.id, testMatch1.id); expect(result.id, testMatch1.id);
expect(result.name, testMatch1.name); expect(result.name, testMatch1.name);
expect(result.createdAt, testMatch1.createdAt); expect(result.createdAt, testMatch1.createdAt);
if (result.group != null) { if (result.group != null) {
expect(result.group!.members.length, testGroup1.members.length); expect(result.group!.members.length, testGroup1.members.length);
for (int i = 0; i < testGroup1.members.length; i++) { for (int i = 0; i < testGroup1.members.length; i++) {
expect(result.group!.members[i].id, testGroup1.members[i].id); expect(result.group!.members[i].id, testGroup1.members[i].id);
expect(result.group!.members[i].name, testGroup1.members[i].name); expect(result.group!.members[i].name, testGroup1.members[i].name);
}
} else {
fail('Group is null');
}
expect(result.players.length, testMatch1.players.length);
for (int i = 0; i < testMatch1.players.length; i++) {
expect(result.players[i].id, testMatch1.players[i].id);
expect(result.players[i].name, testMatch1.players[i].name);
expect(result.players[i].createdAt, testMatch1.players[i].createdAt);
}
});
// Verifies that multiple matches can be added and retrieved with correct groups and players.
test('Adding and fetching multiple matches works correctly', () async {
await database.matchDao.addMatchAsList(
matches: [
testMatch1,
testMatch2,
testMatchOnlyGroup,
testMatchOnlyPlayers,
],
);
final allMatches = await database.matchDao.getAllMatches();
expect(allMatches.length, 4);
final testMatches = {
testMatch1.id: testMatch1,
testMatch2.id: testMatch2,
testMatchOnlyGroup.id: testMatchOnlyGroup,
testMatchOnlyPlayers.id: testMatchOnlyPlayers,
};
for (final match in allMatches) {
final testMatch = testMatches[match.id]!;
// Match-Checks
expect(match.id, testMatch.id);
expect(match.name, testMatch.name);
expect(match.createdAt, testMatch.createdAt);
// Group-Checks
if (testMatch.group != null) {
expect(match.group!.id, testMatch.group!.id);
expect(match.group!.name, testMatch.group!.name);
expect(match.group!.createdAt, testMatch.group!.createdAt);
// Group Members-Checks
expect(match.group!.members.length, testMatch.group!.members.length);
for (int i = 0; i < testMatch.group!.members.length; i++) {
expect(match.group!.members[i].id, testMatch.group!.members[i].id);
expect(
match.group!.members[i].name,
testMatch.group!.members[i].name,
);
expect(
match.group!.members[i].createdAt,
testMatch.group!.members[i].createdAt,
);
} }
} else { } else {
expect(match.group, null); fail('Group is null');
} }
expect(result.players.length, testMatch1.players.length);
// Players-Checks for (int i = 0; i < testMatch1.players.length; i++) {
expect(match.players.length, testMatch.players.length); expect(result.players[i].id, testMatch1.players[i].id);
for (int i = 0; i < testMatch.players.length; i++) { expect(result.players[i].name, testMatch1.players[i].name);
expect(match.players[i].id, testMatch.players[i].id); expect(result.players[i].createdAt, testMatch1.players[i].createdAt);
expect(match.players[i].name, testMatch.players[i].name);
expect(match.players[i].createdAt, testMatch.players[i].createdAt);
} }
} });
test('Adding and fetching multiple matches works correctly', () async {
await database.matchDao.addMatchesAsList(
matches: [
testMatch1,
testMatch2,
testMatchOnlyGroup,
testMatchOnlyPlayers,
],
);
final allMatches = await database.matchDao.getAllMatches();
expect(allMatches.length, 4);
final testMatches = {
testMatch1.id: testMatch1,
testMatch2.id: testMatch2,
testMatchOnlyGroup.id: testMatchOnlyGroup,
testMatchOnlyPlayers.id: testMatchOnlyPlayers,
};
for (final match in allMatches) {
final testMatch = testMatches[match.id]!;
// Match-Checks
expect(match.id, testMatch.id);
expect(match.name, testMatch.name);
expect(match.createdAt, testMatch.createdAt);
// Group-Checks
if (testMatch.group != null) {
expect(match.group!.id, testMatch.group!.id);
expect(match.group!.name, testMatch.group!.name);
expect(match.group!.createdAt, testMatch.group!.createdAt);
// Group Members-Checks
expect(
match.group!.members.length,
testMatch.group!.members.length,
);
for (int i = 0; i < testMatch.group!.members.length; i++) {
expect(
match.group!.members[i].id,
testMatch.group!.members[i].id,
);
expect(
match.group!.members[i].name,
testMatch.group!.members[i].name,
);
expect(
match.group!.members[i].createdAt,
testMatch.group!.members[i].createdAt,
);
}
} else {
expect(match.group, null);
}
// Players-Checks
expect(match.players.length, testMatch.players.length);
for (int i = 0; i < testMatch.players.length; i++) {
expect(match.players[i].id, testMatch.players[i].id);
expect(match.players[i].name, testMatch.players[i].name);
expect(match.players[i].createdAt, testMatch.players[i].createdAt);
}
}
});
test('addMatch() ignores duplicate games', () async {
var added = await database.matchDao.addMatch(match: testMatch1);
expect(added, isTrue);
added = await database.matchDao.addMatch(match: testMatch1);
expect(added, isFalse);
final matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 1);
});
test('addMatchesAsList() returns isFalse for empty list', () async {
var added = await database.matchDao.addMatchesAsList(matches: []);
expect(added, isFalse);
});
test('addMatchesAsList() ignores duplicate games', () async {
final added = await database.matchDao.addMatchesAsList(
matches: [testMatch1, testMatch2, testMatch1],
);
expect(added, isTrue);
final matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 2);
});
}); });
// Verifies that adding the same match twice does not create duplicates. group('READ', () {
test('Adding the same match twice does not create duplicates', () async { test('matchExists() works correctly', () async {
await database.matchDao.addMatch(match: testMatch1); var matchExists = await database.matchDao.matchExists(
await database.matchDao.addMatch(match: testMatch1); matchId: testMatch1.id,
);
expect(matchExists, isFalse);
final matchCount = await database.matchDao.getMatchCount(); await database.matchDao.addMatch(match: testMatch1);
expect(matchCount, 1);
matchExists = await database.matchDao.matchExists(
matchId: testMatch1.id,
);
expect(matchExists, isTrue);
});
test('getGroupMatches() works correctly', () async {
var matches = await database.matchDao.getGroupMatches(
groupId: 'non-existing-id',
);
expect(matches, isEmpty);
await database.matchDao.addMatch(match: testMatch1);
matches = await database.matchDao.getGroupMatches(
groupId: testGroup1.id,
);
expect(matches, isNotEmpty);
final match = matches.first;
expect(match.id, testMatch1.id);
expect(match.group, isNotNull);
expect(match.group!.id, testGroup1.id);
});
}); });
// Verifies that matchExists returns correct boolean based on match presence. group('UPDATE', () {
test('Match existence check works correctly', () async { test('updateMatchName() works correctly', () async {
var matchExists = await database.matchDao.matchExists( await database.matchDao.addMatch(match: testMatch1);
matchId: testMatch1.id,
);
expect(matchExists, false);
await database.matchDao.addMatch(match: testMatch1); const newName = 'New name';
await database.matchDao.updateMatchName(
matchId: testMatch1.id,
name: newName,
);
matchExists = await database.matchDao.matchExists(matchId: testMatch1.id); final fetchedMatch = await database.matchDao.getMatchById(
expect(matchExists, true); matchId: testMatch1.id,
}); );
expect(fetchedMatch.name, newName);
});
// Verifies that deleteMatch removes the match and returns true. test('updateMatchName() does nothing for non-existent match', () async {
test('Deleting a match works correctly', () async { final updated = await database.matchDao.updateMatchName(
await database.matchDao.addMatch(match: testMatch1); matchId: 'non-existing-id',
name: 'New Name',
);
expect(updated, isFalse);
final matchDeleted = await database.matchDao.deleteMatch( final allMatches = await database.matchDao.getAllMatches();
matchId: testMatch1.id, expect(allMatches, isEmpty);
); });
expect(matchDeleted, true);
final matchExists = await database.matchDao.matchExists( test('updateMatchGroup() works correctly', () async {
matchId: testMatch1.id, await database.matchDao.addMatch(match: testMatch1);
); await database.groupDao.addGroup(group: testGroup2);
expect(matchExists, false);
});
// Verifies that getMatchCount returns correct count through add/delete operations. await database.matchDao.updateMatchGroup(
test('Getting the match count works correctly', () async { matchId: testMatch1.id,
var matchCount = await database.matchDao.getMatchCount(); groupId: testGroup2.id,
expect(matchCount, 0); );
await database.matchDao.addMatch(match: testMatch1); final fetchedMatch = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(fetchedMatch.group?.id, testGroup2.id);
});
matchCount = await database.matchDao.getMatchCount(); test('updateMatchGroup() does nothing for non-existent match', () async {
expect(matchCount, 1); final updated = await database.matchDao.updateMatchGroup(
matchId: 'non-existing-id',
groupId: 'group-id',
);
expect(updated, isFalse);
await database.matchDao.addMatch(match: testMatch2); final allMatches = await database.matchDao.getAllMatches();
expect(allMatches, isEmpty);
});
matchCount = await database.matchDao.getMatchCount(); test('removeMatchGroup() works correctly', () async {
expect(matchCount, 2); expect(testMatch1.group, isNotNull);
await database.matchDao.deleteMatch(matchId: testMatch1.id);
matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 1);
await database.matchDao.deleteMatch(matchId: testMatch2.id);
matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 0);
});
// Verifies that updateMatchName correctly updates only the name field.
test('Renaming a match works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
var fetchedMatch = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(fetchedMatch.name, testMatch1.name);
const newName = 'Updated Match Name';
await database.matchDao.updateMatchName(
matchId: testMatch1.id,
newName: newName,
);
fetchedMatch = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(fetchedMatch.name, newName);
});
test('Fetching a winner works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
var fetchedMatch = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(fetchedMatch.mvp, isNotNull);
expect(fetchedMatch.mvp.first.id, testPlayer4.id);
});
test('Setting a winner works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
await database.scoreEntryDao.setWinner(
matchId: testMatch1.id,
playerId: testPlayer5.id,
);
final fetchedMatch = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(fetchedMatch.mvp, isNotNull);
expect(fetchedMatch.mvp.first.id, testPlayer5.id);
});
test(
'removeMatchGroup removes group from match with existing group',
() async {
await database.matchDao.addMatch(match: testMatch1); await database.matchDao.addMatch(match: testMatch1);
final removed = await database.matchDao.removeMatchGroup( final removed = await database.matchDao.removeMatchGroup(
@@ -318,53 +327,149 @@ void main() {
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(updatedMatch.group, null); expect(updatedMatch.group, null);
expect(updatedMatch.game.id, testMatch1.game.id); });
expect(updatedMatch.name, testMatch1.name);
expect(updatedMatch.notes, testMatch1.notes);
},
);
test( test(
'removeMatchGroup on match that already has no group still succeeds', 'removeMatchGroup() on match that already has no group still succeeds',
() async { () async {
await database.matchDao.addMatch(match: testMatchOnlyPlayers); await database.matchDao.addMatch(match: testMatchOnlyPlayers);
final removed = await database.matchDao.removeMatchGroup( final removed = await database.matchDao.removeMatchGroup(
matchId: testMatchOnlyPlayers.id, matchId: testMatchOnlyPlayers.id,
); );
expect(removed, isTrue); expect(removed, isTrue);
final updatedMatch = await database.matchDao.getMatchById( final updatedMatch = await database.matchDao.getMatchById(
matchId: testMatchOnlyPlayers.id, matchId: testMatchOnlyPlayers.id,
); );
expect(updatedMatch.group, null); expect(updatedMatch.group, null);
}, },
);
test('removeMatchGroup on non-existing match returns false', () async {
final removed = await database.matchDao.removeMatchGroup(
matchId: 'non-existing-id',
); );
expect(removed, isFalse);
test(
'removeMatchGroup() on non-existing match returns isFalse',
() async {
final removed = await database.matchDao.removeMatchGroup(
matchId: 'non-existing-id',
);
expect(removed, isFalse);
},
);
test('updateMatchNotes() works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
const newName = 'New name';
await database.matchDao.updateMatchName(
matchId: testMatch1.id,
name: newName,
);
final fetchedMatch = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(fetchedMatch.name, newName);
});
test('updateMatchNotes() does nothing for non-existent game', () async {
final updated = await database.matchDao.updateMatchNotes(
matchId: 'non-existing-id',
notes: 'New notes',
);
expect(updated, isFalse);
final allMatches = await database.matchDao.getAllMatches();
expect(allMatches, isEmpty);
});
test('updateMatchEndedAt() works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
DateTime newEndedAt = DateTime(2030, 1, 1, 12, 0, 0);
print(newEndedAt);
await database.matchDao.updateMatchEndedAt(
matchId: testMatch1.id,
endedAt: newEndedAt,
);
final fetchedMatch = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(fetchedMatch.endedAt, newEndedAt);
});
test('updateMatchEndedAt() does nothing for non-existent game', () async {
final updated = await database.matchDao.updateMatchEndedAt(
matchId: 'non-existing-id',
endedAt: DateTime.now(),
);
expect(updated, isFalse);
final allMatches = await database.matchDao.getAllMatches();
expect(allMatches, isEmpty);
});
test('Getting the match count works correctly', () async {
var matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 0);
await database.matchDao.addMatch(match: testMatch1);
matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 1);
await database.matchDao.addMatch(match: testMatch2);
matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 2);
await database.matchDao.deleteMatch(matchId: testMatch1.id);
matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 1);
await database.matchDao.deleteMatch(matchId: testMatch2.id);
matchCount = await database.matchDao.getMatchCount();
expect(matchCount, 0);
});
}); });
test('Fetching all matches related to a group', () async { group('DELETE', () {
var matches = await database.matchDao.getGroupMatches( test('deleteMatch() works correctly', () async {
groupId: 'non-existing-id', await database.matchDao.addMatch(match: testMatch1);
);
expect(matches, isEmpty); var deleted = await database.matchDao.deleteMatch(
matchId: testMatch1.id,
);
expect(deleted, isTrue);
await database.matchDao.addMatch(match: testMatch1); final matchExists = await database.matchDao.matchExists(
matchId: testMatch1.id,
);
expect(matchExists, isFalse);
matches = await database.matchDao.getGroupMatches(groupId: testGroup1.id); deleted = await database.matchDao.deleteMatch(matchId: testMatch1.id);
expect(deleted, isFalse);
});
expect(matches, isNotEmpty); test('deleteAllMatches() works correctly', () async {
await database.matchDao.addMatchesAsList(
matches: [testMatch1, testMatch2, testMatchOnlyPlayers],
);
final match = matches.first; var count = await database.matchDao.getMatchCount();
expect(match.id, testMatch1.id); expect(count, 3);
expect(match.group, isNotNull);
expect(match.group!.id, testGroup1.id); var deleted = await database.matchDao.deleteAllMatches();
expect(deleted, isTrue);
count = await database.matchDao.getMatchCount();
expect(count, 0);
deleted = await database.matchDao.deleteAllMatches();
expect(deleted, isFalse);
});
}); });
test('getMatchCountByGame() works correctly', () async { test('getMatchCountByGame() works correctly', () async {

View File

@@ -1,3 +1,5 @@
import 'dart:core' hide Match;
import 'package:clock/clock.dart'; import 'package:clock/clock.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
@@ -18,8 +20,11 @@ void main() {
late Team testTeam1; late Team testTeam1;
late Team testTeam2; late Team testTeam2;
late Team testTeam3; late Team testTeam3;
late Game testGame1; late Team testTeam4;
late Game testGame2; late Game testGame;
late Match testMatch1;
late Match testMatch2;
late Match matchWithNoTeams;
final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); final fixedDate = DateTime(2025, 11, 19, 00, 11, 23);
final fakeClock = Clock(() => fixedDate); final fakeClock = Clock(() => fixedDate);
@@ -40,27 +45,35 @@ void main() {
testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]); testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]);
testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]); testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]);
testTeam3 = Team(name: 'Team Gamma', members: [testPlayer1, testPlayer3]); testTeam3 = Team(name: 'Team Gamma', members: [testPlayer1, testPlayer3]);
testGame1 = Game( testTeam4 = Team(name: 'Team Omega', members: [testPlayer2, testPlayer4]);
name: 'Game 1', testGame = Game(
ruleset: Ruleset.singleWinner, name: 'Test Game',
description: 'Test game 1', ruleset: Ruleset.highestScore,
color: GameColor.blue, color: GameColor.blue,
icon: '', icon: '',
); );
testGame2 = Game( testMatch1 = Match(
name: 'Game 2', name: 'Match 1',
ruleset: Ruleset.highestScore, game: testGame,
description: 'Test game 2', players: [],
color: GameColor.red, teams: [testTeam1, testTeam2],
icon: '', );
testMatch2 = Match(
name: 'Match 2',
game: testGame,
players: [],
teams: [testTeam3, testTeam4],
);
matchWithNoTeams = Match(
name: 'Match with no teams',
game: testGame,
players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
); );
}); });
await database.gameDao.addGame(game: testGame);
await database.playerDao.addPlayersAsList( await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
); );
await database.gameDao.addGame(game: testGame1);
await database.gameDao.addGame(game: testGame2);
}); });
tearDown(() async { tearDown(() async {
@@ -68,460 +81,251 @@ void main() {
}); });
group('Team Tests', () { group('Team Tests', () {
// Verifies that a single team can be added and retrieved with all fields intact. group('CREATE', () {
test('Adding and fetching a single team works correctly', () async { test('Adding and fetching a single team works correctly', () async {
final added = await database.teamDao.addTeam(team: testTeam1); await database.matchDao.addMatch(match: matchWithNoTeams);
expect(added, true); final added = await database.teamDao.addTeam(
team: testTeam1,
final fetchedTeam = await database.teamDao.getTeamById( matchId: matchWithNoTeams.id,
teamId: testTeam1.id, );
); expect(added, isTrue);
expect(fetchedTeam.id, testTeam1.id); final fetchedTeam = await database.teamDao.getTeamById(
expect(fetchedTeam.name, testTeam1.name); teamId: testTeam1.id,
expect(fetchedTeam.createdAt, testTeam1.createdAt); );
});
expect(fetchedTeam.id, testTeam1.id);
// Verifies that multiple teams can be added at once and retrieved correctly. expect(fetchedTeam.name, testTeam1.name);
test('Adding and fetching multiple teams works correctly', () async { expect(fetchedTeam.createdAt, testTeam1.createdAt);
await database.teamDao.addTeamsAsList( expect(fetchedTeam.members.length, testTeam1.members.length);
teams: [testTeam1, testTeam2, testTeam3], for (int i = 0; i < fetchedTeam.members.length; i++) {
); expect(fetchedTeam.members[i].id, testTeam1.members[i].id);
expect(fetchedTeam.members[i].name, testTeam1.members[i].name);
final allTeams = await database.teamDao.getAllTeams(); }
expect(allTeams.length, 3); });
final testTeams = { test('Adding and fetching multiple teams works correctly', () async {
testTeam1.id: testTeam1, await database.matchDao.addMatch(match: matchWithNoTeams);
testTeam2.id: testTeam2, await database.teamDao.addTeamsAsList(
testTeam3.id: testTeam3, teams: [testTeam1, testTeam2, testTeam3],
}; matchId: matchWithNoTeams.id,
);
for (final team in allTeams) {
final testTeam = testTeams[team.id]!; final allTeams = await database.teamDao.getAllTeams();
expect(allTeams.length, 3);
expect(team.id, testTeam.id);
expect(team.name, testTeam.name); final testTeams = {
expect(team.createdAt, testTeam.createdAt); testTeam1.id: testTeam1,
} testTeam2.id: testTeam2,
}); testTeam3.id: testTeam3,
};
// Verifies that adding the same team twice does not create duplicates and returns false.
test('Adding the same team twice does not create duplicates', () async { for (final team in allTeams) {
await database.teamDao.addTeam(team: testTeam1); final testTeam = testTeams[team.id]!;
final addedAgain = await database.teamDao.addTeam(team: testTeam1);
expect(team.id, testTeam.id);
expect(addedAgain, false); expect(team.name, testTeam.name);
expect(team.createdAt, testTeam.createdAt);
final teamCount = await database.teamDao.getTeamCount(); }
expect(teamCount, 1); });
});
test('addTeam() ignores duplicates', () async {
// Verifies that teamExists returns correct boolean based on team presence. await database.matchDao.addMatch(match: matchWithNoTeams);
test('Team existence check works correctly', () async { var added = await database.teamDao.addTeam(
var teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); team: testTeam1,
expect(teamExists, false); matchId: matchWithNoTeams.id,
);
await database.teamDao.addTeam(team: testTeam1); expect(added, isTrue);
teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); added = await database.teamDao.addTeam(
expect(teamExists, true); team: testTeam1,
}); matchId: matchWithNoTeams.id,
);
// Verifies that deleteTeam removes the team and returns true. expect(added, isFalse);
test('Deleting a team works correctly', () async {
await database.teamDao.addTeam(team: testTeam1); final teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 1);
final teamDeleted = await database.teamDao.deleteTeam( });
teamId: testTeam1.id,
); test('addTeamsAsList() with empty list returns isFalse', () async {
expect(teamDeleted, true); final added = await database.teamDao.addTeamsAsList(
teams: [],
final teamExists = await database.teamDao.teamExists( matchId: matchWithNoTeams.id,
teamId: testTeam1.id, );
); expect(added, isFalse);
expect(teamExists, false); });
});
test('addTeamsAsList() ignores duplicates', () async {
// Verifies that deleteTeam returns false for a non-existent team ID. await database.matchDao.addMatch(match: matchWithNoTeams);
test('Deleting a non-existent team returns false', () async { final added = await database.teamDao.addTeamsAsList(
final teamDeleted = await database.teamDao.deleteTeam( teams: [testTeam1, testTeam2, testTeam1],
teamId: 'non-existent-id', matchId: matchWithNoTeams.id,
); );
expect(teamDeleted, false); expect(added, isTrue);
});
final teamCount = await database.teamDao.getTeamCount();
// Verifies that getTeamCount returns correct count through add/delete operations. expect(teamCount, 2);
test('Getting the team count works correctly', () async {
var teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 0);
await database.teamDao.addTeam(team: testTeam1);
teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 1);
await database.teamDao.addTeam(team: testTeam2);
teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 2);
await database.teamDao.deleteTeam(teamId: testTeam1.id);
teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 1);
await database.teamDao.deleteTeam(teamId: testTeam2.id);
teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 0);
});
// Verifies that updateTeamName correctly updates only the name field.
test('Updating team name works correctly', () async {
await database.teamDao.addTeam(team: testTeam1);
var fetchedTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id,
);
expect(fetchedTeam.name, testTeam1.name);
const newName = 'Updated Team Name';
await database.teamDao.updateTeamName(
teamId: testTeam1.id,
newName: newName,
);
fetchedTeam = await database.teamDao.getTeamById(teamId: testTeam1.id);
expect(fetchedTeam.name, newName);
});
// Verifies that deleteAllTeams removes all teams from the database.
test('Deleting all teams works correctly', () async {
await database.teamDao.addTeamsAsList(
teams: [testTeam1, testTeam2, testTeam3],
);
var teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 3);
final deleted = await database.teamDao.deleteAllTeams();
expect(deleted, true);
teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 0);
});
// Verifies that deleteAllTeams returns false when no teams exist.
test('Deleting all teams when empty returns false', () async {
final deleted = await database.teamDao.deleteAllTeams();
expect(deleted, false);
});
// Verifies that addTeamsAsList returns false when given an empty list.
test('Adding teams as list with empty list returns false', () async {
final added = await database.teamDao.addTeamsAsList(teams: []);
expect(added, false);
});
// Verifies that addTeamsAsList with duplicate IDs ignores duplicates and keeps the first.
test('Adding teams with duplicate IDs ignores duplicates', () async {
final duplicateTeam = Team(
id: testTeam1.id,
name: 'Duplicate Team',
members: [testPlayer4],
);
await database.teamDao.addTeamsAsList(
teams: [testTeam1, duplicateTeam, testTeam2],
);
final teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 2);
// The first one should be kept (insertOrIgnore)
final fetchedTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id,
);
expect(fetchedTeam.name, testTeam1.name);
});
// Verifies that getAllTeams returns empty list when no teams exist.
test('Getting all teams when empty returns empty list', () async {
final allTeams = await database.teamDao.getAllTeams();
expect(allTeams.isEmpty, true);
});
// Verifies that getTeamById throws exception for non-existent team.
test('Getting non-existent team throws exception', () async {
expect(
() => database.teamDao.getTeamById(teamId: 'non-existent-id'),
throwsA(isA<StateError>()),
);
});
// Verifies that updating team name preserves other fields.
test('Updating team name preserves other team fields', () async {
await database.teamDao.addTeam(team: testTeam1);
final originalTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id,
);
final originalCreatedAt = originalTeam.createdAt;
const newName = 'Brand New Team Name';
await database.teamDao.updateTeamName(
teamId: testTeam1.id,
newName: newName,
);
final updatedTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id,
);
expect(updatedTeam.name, newName);
expect(updatedTeam.id, testTeam1.id);
expect(updatedTeam.createdAt, originalCreatedAt);
});
// Verifies that team name can be updated to an empty string.
test('Updating team name to empty string works', () async {
await database.teamDao.addTeam(team: testTeam1);
await database.teamDao.updateTeamName(teamId: testTeam1.id, newName: '');
final updatedTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id,
);
expect(updatedTeam.name, '');
});
// Verifies that team name can be updated to a very long string.
test('Updating team name to long string works', () async {
await database.teamDao.addTeam(team: testTeam1);
final longName = 'A' * 500; // 500 character name
await database.teamDao.updateTeamName(
teamId: testTeam1.id,
newName: longName,
);
final updatedTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id,
);
expect(updatedTeam.name, longName);
expect(updatedTeam.name.length, 500);
});
// Verifies that updating non-existent team name doesn't throw error.
test('Updating non-existent team name completes without error', () async {
expect(
() => database.teamDao.updateTeamName(
teamId: 'non-existent-id',
newName: 'New Name',
),
returnsNormally,
);
});
// Verifies that deleteTeam only affects the specified team.
test('Deleting one team does not affect other teams', () async {
await database.teamDao.addTeamsAsList(
teams: [testTeam1, testTeam2, testTeam3],
);
await database.teamDao.deleteTeam(teamId: testTeam2.id);
final allTeams = await database.teamDao.getAllTeams();
expect(allTeams.length, 2);
expect(allTeams.any((t) => t.id == testTeam1.id), true);
expect(allTeams.any((t) => t.id == testTeam2.id), false);
expect(allTeams.any((t) => t.id == testTeam3.id), true);
});
// Verifies that teams with overlapping members are independent.
test('Teams with overlapping members are independent', () async {
// Create two matches since player_match has primary key {playerId, matchId}
final match1 = Match(
name: 'Match 1',
game: testGame1,
players: [testPlayer1, testPlayer2],
);
final match2 = Match(
name: 'Match 2',
game: testGame2,
players: [testPlayer1, testPlayer2],
);
await database.matchDao.addMatch(match: match1);
await database.matchDao.addMatch(match: match2);
// Add teams to database
await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam3]);
// Associate players with teams through match1
// testTeam1: player1, player2
await database.playerMatchDao.addPlayerToMatch(
playerId: testPlayer1.id,
matchId: match1.id,
teamId: testTeam1.id,
);
await database.playerMatchDao.addPlayerToMatch(
playerId: testPlayer2.id,
matchId: match1.id,
teamId: testTeam1.id,
);
// Associate players with teams through match2
// testTeam3: player1, player3 (overlapping player1)
await database.playerMatchDao.addPlayerToMatch(
playerId: testPlayer1.id,
matchId: match2.id,
teamId: testTeam3.id,
);
await database.playerMatchDao.addPlayerToMatch(
playerId: testPlayer3.id,
matchId: match2.id,
teamId: testTeam3.id,
);
final team1 = await database.teamDao.getTeamById(teamId: testTeam1.id);
final team3 = await database.teamDao.getTeamById(teamId: testTeam3.id);
expect(team1.members.length, 2);
expect(team3.members.length, 2);
expect(team1.members.any((p) => p.id == testPlayer1.id), true);
expect(team3.members.any((p) => p.id == testPlayer1.id), true);
});
// Verifies that adding teams sequentially works correctly.
test('Adding teams sequentially maintains correct count', () async {
var count = await database.teamDao.getTeamCount();
expect(count, 0);
await database.teamDao.addTeam(team: testTeam1);
count = await database.teamDao.getTeamCount();
expect(count, 1);
await database.teamDao.addTeam(team: testTeam2);
count = await database.teamDao.getTeamCount();
expect(count, 2);
await database.teamDao.addTeam(team: testTeam3);
count = await database.teamDao.getTeamCount();
expect(count, 3);
});
// Verifies that getAllTeams returns all teams with correct data.
test('Getting all teams returns all teams with correct data', () async {
await database.teamDao.addTeamsAsList(
teams: [testTeam1, testTeam2, testTeam3],
);
final allTeams = await database.teamDao.getAllTeams();
expect(allTeams.length, 3);
expect(allTeams.map((t) => t.id).toSet(), {
testTeam1.id,
testTeam2.id,
testTeam3.id,
}); });
}); });
// Verifies that teamExists returns false for deleted teams. group('READ', () {
test('Team existence returns false after deletion', () async { test('getTeamCount works correctly', () async {
await database.teamDao.addTeam(team: testTeam1); var count = await database.teamDao.getTeamCount();
expect(await database.teamDao.teamExists(teamId: testTeam1.id), true); expect(count, 0);
await database.teamDao.deleteTeam(teamId: testTeam1.id); await database.matchDao.addMatch(match: testMatch1);
expect(await database.teamDao.teamExists(teamId: testTeam1.id), false);
count = await database.teamDao.getTeamCount();
expect(count, 2);
await database.teamDao.addTeam(
team: testTeam2,
matchId: matchWithNoTeams.id,
);
count = await database.teamDao.getTeamCount();
expect(count, 2);
await database.teamDao.deleteTeam(teamId: testTeam1.id);
count = await database.teamDao.getTeamCount();
expect(count, 1);
await database.teamDao.deleteTeam(teamId: testTeam2.id);
count = await database.teamDao.getTeamCount();
expect(count, 0);
});
test('teamExists() works correctly', () async {
var teamExists = await database.teamDao.teamExists(
teamId: testTeam1.id,
);
expect(teamExists, isFalse);
await database.matchDao.addMatch(match: matchWithNoTeams);
await database.teamDao.addTeam(
team: testTeam1,
matchId: matchWithNoTeams.id,
);
teamExists = await database.teamDao.teamExists(teamId: testTeam1.id);
expect(teamExists, isTrue);
});
test('getAllTeams() with no teams returns empty list', () async {
final allTeams = await database.teamDao.getAllTeams();
expect(allTeams, isA<List<Team>>());
expect(allTeams.isEmpty, isTrue);
});
test('getAllTeams() works correctly', () async {
await database.matchDao.addMatch(match: matchWithNoTeams);
await database.teamDao.addTeamsAsList(
teams: [testTeam1, testTeam2, testTeam3],
matchId: matchWithNoTeams.id,
);
final allTeams = await database.teamDao.getAllTeams();
expect(allTeams.length, 3);
expect(allTeams.map((t) => t.id).toSet(), {
testTeam1.id,
testTeam2.id,
testTeam3.id,
});
});
test('Getting non-existent team throws exception', () async {
expect(
() => database.teamDao.getTeamById(teamId: 'non-existent-id'),
throwsA(isA<StateError>()),
);
});
}); });
// Verifies that adding multiple teams in batch then deleting returns correct count. group('UPDATED', () {
test('Batch add then partial delete maintains correct count', () async { test('updateTeamName() works correctly', () async {
await database.teamDao.addTeamsAsList( await database.matchDao.addMatch(match: matchWithNoTeams);
teams: [testTeam1, testTeam2, testTeam3],
);
expect(await database.teamDao.getTeamCount(), 3); await database.teamDao.addTeam(
team: testTeam1,
matchId: matchWithNoTeams.id,
);
await database.teamDao.deleteTeam(teamId: testTeam1.id); var fetchedTeam = await database.teamDao.getTeamById(
expect(await database.teamDao.getTeamCount(), 2); teamId: testTeam1.id,
);
expect(fetchedTeam.name, testTeam1.name);
await database.teamDao.deleteTeam(teamId: testTeam3.id); const newName = 'New name';
expect(await database.teamDao.getTeamCount(), 1); await database.teamDao.updateTeamName(
teamId: testTeam1.id,
name: newName,
);
fetchedTeam = await database.teamDao.getTeamById(teamId: testTeam1.id);
expect(fetchedTeam.name, newName);
});
test('updateTeamName() does nothing for non-existent team', () async {
final updated = await database.teamDao.updateTeamName(
teamId: 'non-existing-id',
name: 'New Name',
);
expect(updated, isFalse);
final allTeams = await database.teamDao.getAllTeams();
expect(allTeams, isEmpty);
});
}); });
// Verifies that deleteAllTeams with single team works. group('DELETE', () {
test('Deleting all teams with single team returns true', () async { test('deleteTeam() works correctly', () async {
await database.teamDao.addTeam(team: testTeam1); await database.matchDao.addMatch(match: testMatch1);
expect(await database.teamDao.getTeamCount(), 1); await database.matchDao.addMatch(match: matchWithNoTeams);
final deleted = await database.teamDao.deleteAllTeams(); await database.teamDao.addTeam(
expect(deleted, true); team: testTeam1,
expect(await database.teamDao.getTeamCount(), 0); matchId: matchWithNoTeams.id,
}); );
// Verifies that addTeam after deleteAllTeams works correctly. final deleted = await database.teamDao.deleteTeam(teamId: testTeam1.id);
test('Adding team after deleteAllTeams works correctly', () async { expect(deleted, isTrue);
await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam2]);
expect(await database.teamDao.getTeamCount(), 2);
await database.teamDao.deleteAllTeams(); final teamExists = await database.teamDao.teamExists(
expect(await database.teamDao.getTeamCount(), 0); teamId: testTeam1.id,
);
expect(teamExists, isFalse);
});
final added = await database.teamDao.addTeam(team: testTeam3); test('Deleting a non-existent team returns isFalse', () async {
expect(added, true); final deleted = await database.teamDao.deleteTeam(
expect(await database.teamDao.getTeamCount(), 1); teamId: 'non-existent-id',
);
expect(deleted, isFalse);
});
final fetchedTeam = await database.teamDao.getTeamById( test('deleteAllTeams() works correctly', () async {
teamId: testTeam3.id, await database.matchDao.addMatchesAsList(
); matches: [testMatch1, testMatch2],
expect(fetchedTeam.name, testTeam3.name); );
}); var teamCount = await database.teamDao.getTeamCount();
expect(teamCount, 4);
// Verifies that addTeamsAsList with partial duplicates ignores duplicates. final deleted = await database.teamDao.deleteAllTeams();
test('Adding teams with some duplicates ignores only duplicates', () async { expect(deleted, isTrue);
await database.teamDao.addTeam(team: testTeam1);
final duplicateTeam1 = Team( teamCount = await database.teamDao.getTeamCount();
id: testTeam1.id, expect(teamCount, 0);
name: 'Different Name', });
members: [testPlayer3],
);
await database.teamDao.addTeamsAsList( test('deleteAllTeams() with empty list returns false', () async {
teams: [duplicateTeam1, testTeam2, testTeam3], final deleted = await database.teamDao.deleteAllTeams();
); expect(deleted, isFalse);
});
final allTeams = await database.teamDao.getAllTeams();
expect(allTeams.length, 3);
// Verify testTeam1 retained original name (was inserted first)
final team1 = await database.teamDao.getTeamById(teamId: testTeam1.id);
expect(team1.name, testTeam1.name);
});
// Verifies that team IDs are preserved correctly.
test('Team IDs are preserved through add and retrieve', () async {
await database.teamDao.addTeam(team: testTeam1);
final fetchedTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id,
);
expect(fetchedTeam.id, testTeam1.id);
});
// Verifies that createdAt timestamps are preserved.
test('Team createdAt timestamps are preserved', () async {
await database.teamDao.addTeam(team: testTeam1);
final fetchedTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id,
);
expect(fetchedTeam.createdAt, testTeam1.createdAt);
}); });
}); });
} }

View File

@@ -24,6 +24,7 @@ void main() {
withClock(fakeClock, () { withClock(fakeClock, () {
testGame1 = Game( testGame1 = Game(
id: 'game1',
name: 'Chess', name: 'Chess',
ruleset: Ruleset.singleWinner, ruleset: Ruleset.singleWinner,
description: 'A classic strategy game', description: 'A classic strategy game',
@@ -54,492 +55,350 @@ void main() {
}); });
group('Game Tests', () { group('Game Tests', () {
// Verifies that getAllGames returns an empty list when the database has no games. group('CREATE', () {
test('getAllGames returns empty list when no games exist', () async { test('Adding and fetching a single game works correctly', () async {
final allGames = await database.gameDao.getAllGames(); final added = await database.gameDao.addGame(game: testGame1);
expect(allGames, isEmpty); expect(added, isTrue);
});
// Verifies that a single game can be added and retrieved with all fields intact. final game = await database.gameDao.getGameById(gameId: testGame1.id);
test('Adding and fetching a single game works correctly', () async { expect(game.id, testGame1.id);
await database.gameDao.addGame(game: testGame1); expect(game.name, testGame1.name);
expect(game.ruleset, testGame1.ruleset);
expect(game.description, testGame1.description);
expect(game.color, testGame1.color);
expect(game.icon, testGame1.icon);
expect(game.createdAt, testGame1.createdAt);
});
final allGames = await database.gameDao.getAllGames(); test('Adding and fetching multiple games works correctly', () async {
expect(allGames.length, 1); final added = await database.gameDao.addGamesAsList(
expect(allGames.first.id, testGame1.id); games: [testGame1, testGame2, testGame3],
expect(allGames.first.name, testGame1.name); );
expect(allGames.first.ruleset, testGame1.ruleset); expect(added, isTrue);
expect(allGames.first.description, testGame1.description);
expect(allGames.first.color, testGame1.color);
expect(allGames.first.icon, testGame1.icon);
expect(allGames.first.createdAt, testGame1.createdAt);
});
// Verifies that multiple games can be added and retrieved correctly. final allGames = await database.gameDao.getAllGames();
test('Adding and fetching multiple games works correctly', () async { expect(allGames.length, 3);
await database.gameDao.addGame(game: testGame1);
await database.gameDao.addGame(game: testGame2);
await database.gameDao.addGame(game: testGame3);
final allGames = await database.gameDao.getAllGames(); // Map for connecting fetched games with expected games
expect(allGames.length, 3); final testGames = {
testGame1.id: testGame1,
testGame2.id: testGame2,
testGame3.id: testGame3,
};
final names = allGames.map((g) => g.name).toList(); for (final game in allGames) {
expect(names, containsAll(['Chess', 'Poker', 'Monopoly'])); final testGame = testGames[game.id]!;
});
// Verifies that getGameById returns the correct game with all properties. expect(game.id, testGame.id);
test('getGameById returns correct game', () async { expect(game.name, testGame.name);
await database.gameDao.addGame(game: testGame1); expect(game.createdAt, testGame.createdAt);
await database.gameDao.addGame(game: testGame2); expect(game.description, testGame.description);
expect(game.ruleset, testGame.ruleset);
expect(game.color, testGame.color);
expect(game.icon, testGame.icon);
}
});
final game = await database.gameDao.getGameById(gameId: testGame2.id); test('addGamesAsList() returns false for empty list', () async {
expect(game.id, testGame2.id); final result = await database.gameDao.addGamesAsList(games: []);
expect(game.name, testGame2.name); expect(result, isFalse);
expect(game.ruleset, testGame2.ruleset);
expect(game.description, testGame2.description);
expect(game.color, testGame2.color);
expect(game.icon, testGame2.icon);
});
// Verifies that getGameById throws a StateError when the game doesn't exist. final allGames = await database.gameDao.getAllGames();
test('getGameById throws exception for non-existent game', () async { expect(allGames.length, 0);
expect( });
() => database.gameDao.getGameById(gameId: 'non-existent-id'),
throwsA(isA<StateError>()), test('addGamesAsList() ignores duplicate games', () async {
final added = await database.gameDao.addGamesAsList(
games: [testGame1, testGame2, testGame1],
);
expect(added, isTrue);
final allGames = await database.gameDao.getAllGames();
expect(allGames.length, 2);
});
test(
'Game with special characters in name is stored correctly',
() async {
final specialGame = Game(
name: 'Game\'s & "Special" <Name>',
ruleset: Ruleset.multipleWinners,
description: 'Description with émojis 🎮🎲',
color: GameColor.purple,
icon: '',
);
await database.gameDao.addGame(game: specialGame);
final fetchedGame = await database.gameDao.getGameById(
gameId: specialGame.id,
);
expect(fetchedGame.name, 'Game\'s & "Special" <Name>');
expect(fetchedGame.description, 'Description with émojis 🎮🎲');
},
); );
}); });
// Verifies that addGame returns true when a game is successfully added. group('READ', () {
test('addGame returns true when game is added successfully', () async { test('getGameById() works correctly', () async {
final result = await database.gameDao.addGame(game: testGame1); await database.gameDao.addGame(game: testGame1);
expect(result, true);
final allGames = await database.gameDao.getAllGames(); final game = await database.gameDao.getGameById(gameId: testGame1.id);
expect(allGames.length, 1); expect(game.id, testGame1.id);
expect(game.name, testGame1.name);
expect(game.ruleset, testGame1.ruleset);
expect(game.description, testGame1.description);
expect(game.color, testGame1.color);
expect(game.icon, testGame1.icon);
});
test('getGameById() throws exception for non-existent game', () async {
expect(
() => database.gameDao.getGameById(gameId: 'non-existent-id'),
throwsA(isA<StateError>()),
);
});
test('gameExists() works correctly', () async {
var exists = await database.gameDao.gameExists(gameId: testGame1.id);
expect(exists, isFalse);
await database.gameDao.addGame(game: testGame1);
exists = await database.gameDao.gameExists(gameId: testGame1.id);
expect(exists, isTrue);
});
test('getAllGames() returns empty list when no games exist', () async {
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
test('getGameCount() works correctly', () async {
var count = await database.gameDao.getGameCount();
expect(count, 0);
await database.gameDao.addGame(game: testGame1);
count = await database.gameDao.getGameCount();
expect(count, 1);
await database.gameDao.addGame(game: testGame2);
count = await database.gameDao.getGameCount();
expect(count, 2);
await database.gameDao.deleteGame(gameId: testGame1.id);
count = await database.gameDao.getGameCount();
expect(count, 1);
});
}); });
// Verifies that addGame returns false when trying to add a duplicate game. group('UPDATE', () {
test('addGame returns false when game already exists', () async { test('updateGameName() works correctly', () async {
final firstAdd = await database.gameDao.addGame(game: testGame1); await database.gameDao.addGame(game: testGame1);
expect(firstAdd, true); const newName = 'New name';
final secondAdd = await database.gameDao.addGame(game: testGame1); final updated = await database.gameDao.updateGameName(
expect(secondAdd, false); gameId: testGame1.id,
name: newName,
);
expect(updated, isTrue);
final allGames = await database.gameDao.getAllGames(); final updatedGame = await database.gameDao.getGameById(
expect(allGames.length, 1); gameId: testGame1.id,
);
expect(updatedGame.name, newName);
});
test('updateGameName() does nothing for non-existent game', () async {
final updated = await database.gameDao.updateGameName(
gameId: 'non-existent-id',
name: 'New name',
);
expect(updated, isFalse);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
test('updateGameRuleset() works correctly', () async {
await database.gameDao.addGame(game: testGame1);
const ruleset = Ruleset.highestScore;
final updated = await database.gameDao.updateGameRuleset(
gameId: testGame1.id,
ruleset: ruleset,
);
expect(updated, isTrue);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.ruleset, ruleset);
});
test('updateGameRuleset() does nothing for non-existent game', () async {
final updated = await database.gameDao.updateGameRuleset(
gameId: 'non-existent-id',
ruleset: Ruleset.lowestScore,
);
expect(updated, isFalse);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
test('updateGameDescription() works correctly', () async {
await database.gameDao.addGame(game: testGame1);
const newDescription = 'New description';
final updated = await database.gameDao.updateGameDescription(
gameId: testGame1.id,
description: newDescription,
);
expect(updated, isTrue);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.description, newDescription);
});
test(
'updateGameDescription() does nothing for non-existent game',
() async {
final updated = await database.gameDao.updateGameDescription(
gameId: 'non-existent-id',
description: 'New description',
);
expect(updated, isFalse);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
},
);
test('updateGameColor() works correctly', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameColor(
gameId: testGame1.id,
color: GameColor.green,
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.color, GameColor.green);
});
test('updateGameColor() does nothing for non-existent game', () async {
final updated = await database.gameDao.updateGameColor(
gameId: 'non-existent-id',
color: GameColor.green,
);
expect(updated, isFalse);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
test('updateGameIcon() works correctly', () async {
await database.gameDao.addGame(game: testGame1);
const newIcon = 'new_chess_icon';
final updated = await database.gameDao.updateGameIcon(
gameId: testGame1.id,
icon: newIcon,
);
expect(updated, isTrue);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.icon, newIcon);
});
test('updateGameIcon() does nothing for non-existent game', () async {
final updated = await database.gameDao.updateGameIcon(
gameId: 'non-existent-id',
icon: 'New icon',
);
expect(updated, isFalse);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
test('Multiple updates to the same game work correctly', () async {
await database.gameDao.addGame(game: testGame1);
const newName = 'New name';
await database.gameDao.updateGameName(
gameId: testGame1.id,
name: newName,
);
const newGameColor = GameColor.teal;
await database.gameDao.updateGameColor(
gameId: testGame1.id,
color: newGameColor,
);
const newDescription = 'New description';
await database.gameDao.updateGameDescription(
gameId: testGame1.id,
description: newDescription,
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
// Changed values
expect(updatedGame.name, newName);
expect(updatedGame.color, newGameColor);
expect(updatedGame.description, newDescription);
// Staying the same
expect(updatedGame.ruleset, testGame1.ruleset);
expect(updatedGame.icon, testGame1.icon);
});
}); });
group('DELETE', () {
// Verifies that a game with empty optional fields can be added and retrieved. test('deleteGame() works correctly', () async {
test('addGame handles game with null optional fields', () async { await database.gameDao.addGame(game: testGame1);
final gameWithNulls = Game(
name: 'Simple Game', final deleted = await database.gameDao.deleteGame(gameId: testGame1.id);
ruleset: Ruleset.lowestScore, expect(deleted, isTrue);
description: 'A simple game',
color: GameColor.green, final allGames = await database.gameDao.getAllGames();
icon: '', expect(allGames, isEmpty);
); });
final result = await database.gameDao.addGame(game: gameWithNulls);
expect(result, true); test('deleteGame() returns false for non-existent game', () async {
final deleted = await database.gameDao.deleteGame(
final fetchedGame = await database.gameDao.getGameById( gameId: 'non-existent-id',
gameId: gameWithNulls.id, );
); expect(deleted, isFalse);
expect(fetchedGame.name, 'Simple Game'); });
expect(fetchedGame.description, 'A simple game');
expect(fetchedGame.color, GameColor.green); test('deleteAllGames() removes all games', () async {
expect(fetchedGame.icon, ''); await database.gameDao.addGamesAsList(
}); games: [testGame1, testGame2, testGame3],
);
// Verifies that multiple games can be added at once using addGamesAsList.
test('addGamesAsList adds multiple games correctly', () async { var count = await database.gameDao.getGameCount();
final result = await database.gameDao.addGamesAsList( expect(count, 3);
games: [testGame1, testGame2, testGame3],
); final deleted = await database.gameDao.deleteAllGames();
expect(result, true); expect(deleted, isTrue);
final allGames = await database.gameDao.getAllGames(); count = await database.gameDao.getGameCount();
expect(allGames.length, 3); expect(count, 0);
}); });
// Verifies that addGamesAsList returns false when given an empty list. test('deleteAllGames() returns false when no games exist', () async {
test('addGamesAsList returns false for empty list', () async { final deleted = await database.gameDao.deleteAllGames();
final result = await database.gameDao.addGamesAsList(games: []); expect(deleted, isFalse);
expect(result, false); });
final allGames = await database.gameDao.getAllGames();
expect(allGames.length, 0);
});
// Verifies that addGamesAsList ignores duplicate games when adding.
test('addGamesAsList ignores duplicate games', () async {
await database.gameDao.addGame(game: testGame1);
final result = await database.gameDao.addGamesAsList(
games: [testGame1, testGame2],
);
expect(result, true);
final allGames = await database.gameDao.getAllGames();
expect(allGames.length, 2);
});
// Verifies that deleteGame returns true and removes the game from database.
test('deleteGame returns true when game is deleted', () async {
await database.gameDao.addGame(game: testGame1);
final result = await database.gameDao.deleteGame(gameId: testGame1.id);
expect(result, true);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
// Verifies that deleteGame returns false for a non-existent game ID.
test('deleteGame returns false for non-existent game', () async {
final result = await database.gameDao.deleteGame(
gameId: 'non-existent-id',
);
expect(result, false);
});
// Verifies that deleteGame only removes the specified game, leaving others intact.
test('deleteGame only deletes the specified game', () async {
await database.gameDao.addGamesAsList(
games: [testGame1, testGame2, testGame3],
);
await database.gameDao.deleteGame(gameId: testGame2.id);
final allGames = await database.gameDao.getAllGames();
expect(allGames.length, 2);
expect(allGames.any((g) => g.id == testGame2.id), false);
expect(allGames.any((g) => g.id == testGame1.id), true);
expect(allGames.any((g) => g.id == testGame3.id), true);
});
// Verifies that gameExists returns true when the game exists in database.
test('gameExists returns true for existing game', () async {
await database.gameDao.addGame(game: testGame1);
final exists = await database.gameDao.gameExists(gameId: testGame1.id);
expect(exists, true);
});
// Verifies that gameExists returns false for a non-existent game ID.
test('gameExists returns false for non-existent game', () async {
final exists = await database.gameDao.gameExists(
gameId: 'non-existent-id',
);
expect(exists, false);
});
// Verifies that gameExists returns false after a game has been deleted.
test('gameExists returns false after game is deleted', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.deleteGame(gameId: testGame1.id);
final exists = await database.gameDao.gameExists(gameId: testGame1.id);
expect(exists, false);
});
// Verifies that updateGameName correctly updates only the name field.
test('updateGameName updates the name correctly', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameName(
gameId: testGame1.id,
newName: 'Updated Chess',
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.name, 'Updated Chess');
expect(updatedGame.ruleset, testGame1.ruleset);
});
// Verifies that updateGameName does nothing when game doesn't exist.
test('updateGameName does nothing for non-existent game', () async {
await database.gameDao.updateGameName(
gameId: 'non-existent-id',
newName: 'New Name',
);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
// Verifies that updateGameRuleset correctly updates only the ruleset field.
test('updateGameRuleset updates the ruleset correctly', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameRuleset(
gameId: testGame1.id,
newRuleset: Ruleset.highestScore,
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.ruleset, Ruleset.highestScore);
expect(updatedGame.name, testGame1.name);
});
// Verifies that updateGameRuleset does nothing when game doesn't exist.
test('updateGameRuleset does nothing for non-existent game', () async {
await database.gameDao.updateGameRuleset(
gameId: 'non-existent-id',
newRuleset: Ruleset.lowestScore,
);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
// Verifies that updateGameDescription correctly updates the description.
test('updateGameDescription updates the description correctly', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameDescription(
gameId: testGame1.id,
newDescription: 'An updated description',
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.description, 'An updated description');
});
// Verifies that updateGameDescription can set the description to an empty string.
test('updateGameDescription can set description to empty string', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameDescription(
gameId: testGame1.id,
newDescription: '',
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.description, '');
});
// Verifies that updateGameDescription does nothing when game doesn't exist.
test('updateGameDescription does nothing for non-existent game', () async {
await database.gameDao.updateGameDescription(
gameId: 'non-existent-id',
newDescription: 'New Description',
);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
// Verifies that updateGameColor correctly updates the color value.
test('updateGameColor updates the color correctly', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameColor(
gameId: testGame1.id,
newColor: GameColor.green,
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.color, GameColor.green);
});
// Verifies that updateGameColor does nothing when game doesn't exist.
test('updateGameColor does nothing for non-existent game', () async {
await database.gameDao.updateGameColor(
gameId: 'non-existent-id',
newColor: GameColor.green,
);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
// Verifies that updateGameIcon correctly updates the icon value.
test('updateGameIcon updates the icon correctly', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameIcon(
gameId: testGame1.id,
newIcon: 'new_chess_icon',
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.icon, 'new_chess_icon');
});
// Verifies that updateGameIcon can update the icon.
test('updateGameIcon updates icon correctly', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameIcon(
gameId: testGame1.id,
newIcon: 'new_icon',
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.icon, 'new_icon');
});
// Verifies that updateGameIcon does nothing when game doesn't exist.
test('updateGameIcon does nothing for non-existent game', () async {
await database.gameDao.updateGameIcon(
gameId: 'non-existent-id',
newIcon: 'some_icon',
);
final allGames = await database.gameDao.getAllGames();
expect(allGames, isEmpty);
});
// Verifies that getGameCount returns 0 when no games exist.
test('getGameCount returns 0 when no games exist', () async {
final count = await database.gameDao.getGameCount();
expect(count, 0);
});
// Verifies that getGameCount returns the correct count after adding games.
test('getGameCount returns correct count after adding games', () async {
await database.gameDao.addGamesAsList(
games: [testGame1, testGame2, testGame3],
);
final count = await database.gameDao.getGameCount();
expect(count, 3);
});
// Verifies that getGameCount updates correctly after deleting a game.
test('getGameCount updates correctly after deletion', () async {
await database.gameDao.addGamesAsList(games: [testGame1, testGame2]);
final countBefore = await database.gameDao.getGameCount();
expect(countBefore, 2);
await database.gameDao.deleteGame(gameId: testGame1.id);
final countAfter = await database.gameDao.getGameCount();
expect(countAfter, 1);
});
// Verifies that deleteAllGames removes all games from the database.
test('deleteAllGames removes all games', () async {
await database.gameDao.addGamesAsList(
games: [testGame1, testGame2, testGame3],
);
final countBefore = await database.gameDao.getGameCount();
expect(countBefore, 3);
final result = await database.gameDao.deleteAllGames();
expect(result, true);
final countAfter = await database.gameDao.getGameCount();
expect(countAfter, 0);
});
// Verifies that deleteAllGames returns false when no games exist.
test('deleteAllGames returns false when no games exist', () async {
final result = await database.gameDao.deleteAllGames();
expect(result, false);
});
// Verifies that games with special characters (quotes, emojis) are stored correctly.
test('Game with special characters in name is stored correctly', () async {
final specialGame = Game(
name: 'Game\'s & "Special" <Name>',
ruleset: Ruleset.multipleWinners,
description: 'Description with émojis 🎮🎲',
color: GameColor.purple,
icon: '',
);
await database.gameDao.addGame(game: specialGame);
final fetchedGame = await database.gameDao.getGameById(
gameId: specialGame.id,
);
expect(fetchedGame.name, 'Game\'s & "Special" <Name>');
expect(fetchedGame.description, 'Description with émojis 🎮🎲');
});
// Verifies that games with empty string fields are stored and retrieved correctly.
test('Game with empty string fields is stored correctly', () async {
final emptyGame = Game(
name: '',
ruleset: Ruleset.singleWinner,
description: '',
icon: '',
color: GameColor.red,
);
await database.gameDao.addGame(game: emptyGame);
final fetchedGame = await database.gameDao.getGameById(
gameId: emptyGame.id,
);
expect(fetchedGame.name, '');
expect(fetchedGame.ruleset, Ruleset.singleWinner);
expect(fetchedGame.description, '');
expect(fetchedGame.icon, '');
});
// Verifies that games with very long strings (10000 chars) are handled correctly.
test('Game with very long strings is stored correctly', () async {
final longString = 'A' * 10000;
final longGame = Game(
name: longString,
description: longString,
ruleset: Ruleset.multipleWinners,
color: GameColor.yellow,
icon: '',
);
await database.gameDao.addGame(game: longGame);
final fetchedGame = await database.gameDao.getGameById(
gameId: longGame.id,
);
expect(fetchedGame.name.length, 10000);
expect(fetchedGame.description.length, 10000);
expect(fetchedGame.ruleset, Ruleset.multipleWinners);
});
// Verifies that multiple sequential updates to the same game work correctly.
test('Multiple updates to the same game work correctly', () async {
await database.gameDao.addGame(game: testGame1);
await database.gameDao.updateGameName(
gameId: testGame1.id,
newName: 'Updated Name',
);
await database.gameDao.updateGameColor(
gameId: testGame1.id,
newColor: GameColor.teal,
);
await database.gameDao.updateGameDescription(
gameId: testGame1.id,
newDescription: 'Updated Description',
);
final updatedGame = await database.gameDao.getGameById(
gameId: testGame1.id,
);
expect(updatedGame.name, 'Updated Name');
expect(updatedGame.color, GameColor.teal);
expect(updatedGame.description, 'Updated Description');
expect(updatedGame.ruleset, testGame1.ruleset);
expect(updatedGame.icon, testGame1.icon);
}); });
}); });
} }

View File

@@ -24,8 +24,8 @@ void main() {
); );
withClock(fakeClock, () { withClock(fakeClock, () {
testPlayer1 = Player(name: 'Test Player'); testPlayer1 = Player(name: 'Anna', description: 'First test player');
testPlayer2 = Player(name: 'Second Player'); testPlayer2 = Player(name: 'Bob', description: 'Second test player');
testPlayer3 = Player(name: 'Charlie'); testPlayer3 = Player(name: 'Charlie');
testPlayer4 = Player(name: 'Diana'); testPlayer4 = Player(name: 'Diana');
}); });
@@ -35,355 +35,314 @@ void main() {
}); });
group('Player Tests', () { group('Player Tests', () {
// Verifies that players can be added and retrieved with all fields intact. group('CREATE', () {
test('Adding and fetching single player works correctly', () async { test('Adding and fetching single player works correctly', () async {
await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer1);
await database.playerDao.addPlayer(player: testPlayer2); await database.playerDao.addPlayer(player: testPlayer2);
final allPlayers = await database.playerDao.getAllPlayers(); final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers.length, 2); expect(allPlayers.length, 2);
final fetchedPlayer1 = allPlayers.firstWhere( final fetchedPlayer1 = allPlayers.firstWhere(
(g) => g.id == testPlayer1.id, (g) => g.id == testPlayer1.id,
); );
expect(fetchedPlayer1.name, testPlayer1.name); expect(fetchedPlayer1.name, testPlayer1.name);
expect(fetchedPlayer1.createdAt, testPlayer1.createdAt); expect(fetchedPlayer1.createdAt, testPlayer1.createdAt);
expect(fetchedPlayer1.description, testPlayer1.description);
final fetchedPlayer2 = allPlayers.firstWhere( final fetchedPlayer2 = allPlayers.firstWhere(
(g) => g.id == testPlayer2.id, (g) => g.id == testPlayer2.id,
); );
expect(fetchedPlayer2.name, testPlayer2.name); expect(fetchedPlayer2.name, testPlayer2.name);
expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); expect(fetchedPlayer2.createdAt, testPlayer2.createdAt);
}); expect(fetchedPlayer2.description, testPlayer2.description);
});
// Verifies that multiple players can be added at once and retrieved correctly. test('Adding and fetching multiple players works correctly', () async {
test('Adding and fetching multiple players works correctly', () async { await database.playerDao.addPlayersAsList(
await database.playerDao.addPlayersAsList( players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers.length, 4);
// Map for connecting fetched players with expected players
final testPlayers = {
testPlayer1.id: testPlayer1,
testPlayer2.id: testPlayer2,
testPlayer3.id: testPlayer3,
testPlayer4.id: testPlayer4,
};
for (final player in allPlayers) {
final testPlayer = testPlayers[player.id]!;
expect(player.id, testPlayer.id);
expect(player.name, testPlayer.name);
expect(player.createdAt, testPlayer.createdAt);
}
});
// Verifies that adding the same player twice does not create duplicates.
test('Adding the same player twice does not create duplicates', () async {
await database.playerDao.addPlayer(player: testPlayer1);
await database.playerDao.addPlayer(player: testPlayer1);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers.length, 1);
});
// Verifies that playerExists returns correct boolean based on player presence.
test('Player existence check works correctly', () async {
var playerExists = await database.playerDao.playerExists(
playerId: testPlayer1.id,
);
expect(playerExists, false);
await database.playerDao.addPlayer(player: testPlayer1);
playerExists = await database.playerDao.playerExists(
playerId: testPlayer1.id,
);
expect(playerExists, true);
});
// Verifies that deletePlayer removes the player and returns true.
test('Deleting a player works correctly', () async {
await database.playerDao.addPlayer(player: testPlayer1);
final playerDeleted = await database.playerDao.deletePlayer(
playerId: testPlayer1.id,
);
expect(playerDeleted, true);
final playerExists = await database.playerDao.playerExists(
playerId: testPlayer1.id,
);
expect(playerExists, false);
});
// Verifies that updatePlayerName correctly updates only the name field.
test('Updating a player name works correctly', () async {
await database.playerDao.addPlayer(player: testPlayer1);
const newPlayerName = 'new player name';
await database.playerDao.updatePlayerName(
playerId: testPlayer1.id,
newName: newPlayerName,
);
final result = await database.playerDao.getPlayerById(
playerId: testPlayer1.id,
);
expect(result.name, newPlayerName);
});
// Verifies that getPlayerCount returns correct count through add/delete operations.
test('Getting the player count works correctly', () async {
var playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 0);
await database.playerDao.addPlayer(player: testPlayer1);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 1);
await database.playerDao.addPlayer(player: testPlayer2);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 2);
await database.playerDao.deletePlayer(playerId: testPlayer1.id);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 1);
await database.playerDao.deletePlayer(playerId: testPlayer2.id);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 0);
});
// Verifies that getAllPlayers returns an empty list when no players exist.
test('getAllPlayers returns empty list when no players exist', () async {
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers, isEmpty);
});
// Verifies that getPlayerById returns the correct player.
test('getPlayerById returns correct player', () async {
await database.playerDao.addPlayer(player: testPlayer1);
await database.playerDao.addPlayer(player: testPlayer2);
final fetchedPlayer = await database.playerDao.getPlayerById(
playerId: testPlayer1.id,
);
expect(fetchedPlayer.id, testPlayer1.id);
expect(fetchedPlayer.name, testPlayer1.name);
expect(fetchedPlayer.createdAt, testPlayer1.createdAt);
expect(fetchedPlayer.description, testPlayer1.description);
});
// Verifies that getPlayerById throws StateError for non-existent player ID.
test('getPlayerById throws exception for non-existent player', () async {
expect(
() => database.playerDao.getPlayerById(playerId: 'non-existent-id'),
throwsA(isA<StateError>()),
);
});
// Verifies that addPlayer returns false when trying to add a duplicate player.
test('addPlayer returns false when player already exists', () async {
final firstAdd = await database.playerDao.addPlayer(player: testPlayer1);
expect(firstAdd, true);
final secondAdd = await database.playerDao.addPlayer(player: testPlayer1);
expect(secondAdd, false);
});
// Verifies that addPlayersAsList handles empty list correctly.
test('addPlayersAsList handles empty list correctly', () async {
final result = await database.playerDao.addPlayersAsList(players: []);
expect(result, false);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers, isEmpty);
});
// Verifies that addPlayersAsList ignores duplicate player IDs.
test('addPlayersAsList with duplicate IDs ignores duplicates', () async {
await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer1, testPlayer2],
);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers.length, 2);
});
// Verifies that deletePlayer returns false for non-existent player.
test('deletePlayer returns false for non-existent player', () async {
final result = await database.playerDao.deletePlayer(
playerId: 'non-existent-id',
);
expect(result, false);
});
// Verifies that updatePlayerName does nothing for non-existent player (no exception).
test('updatePlayerName does nothing for non-existent player', () async {
// Should not throw, just do nothing
await database.playerDao.updatePlayerName(
playerId: 'non-existent-id',
newName: 'New Name',
);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers, isEmpty);
});
// Verifies that deleteAllPlayers removes all players.
test('deleteAllPlayers removes all players', () async {
await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer2, testPlayer3],
);
var playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 3);
final result = await database.playerDao.deleteAllPlayers();
expect(result, true);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 0);
});
// Verifies that deleteAllPlayers returns false when no players exist.
test('deleteAllPlayers returns false when no players exist', () async {
final result = await database.playerDao.deleteAllPlayers();
expect(result, false);
});
// Verifies that a player with special characters in name is stored correctly.
test(
'Player with special characters in name is stored correctly',
() async {
final specialPlayer = Player(
name: 'Test!@#\$%^&*()_+-=[]{}|;\':",.<>?/`~',
description: '',
); );
await database.playerDao.addPlayer(player: specialPlayer); final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers.length, 4);
// Map for connecting fetched players with expected players
final testPlayers = {
testPlayer1.id: testPlayer1,
testPlayer2.id: testPlayer2,
testPlayer3.id: testPlayer3,
testPlayer4.id: testPlayer4,
};
for (final player in allPlayers) {
final testPlayer = testPlayers[player.id]!;
expect(player.id, testPlayer.id);
expect(player.name, testPlayer.name);
expect(player.createdAt, testPlayer.createdAt);
expect(player.description, testPlayer.description);
}
});
test('Adding the same player twice does not create duplicates', () async {
await database.playerDao.addPlayer(player: testPlayer1);
await database.playerDao.addPlayer(player: testPlayer1);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers.length, 1);
});
test('addPlayer() returns false when player already exists', () async {
var added = await database.playerDao.addPlayer(player: testPlayer1);
expect(added, isTrue);
added = await database.playerDao.addPlayer(player: testPlayer1);
expect(added, isFalse);
});
test('addPlayersAsList() handles empty list correctly', () async {
final added = await database.playerDao.addPlayersAsList(players: []);
expect(added, isFalse);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers, isEmpty);
});
test(
'addPlayersAsList() with duplicate IDs ignores duplicates',
() async {
await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer1, testPlayer2],
);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers.length, 2);
},
);
test(
'Player with special characters in name is stored correctly',
() async {
final specialPlayer = Player(
name: 'Test!@#\$%^&*()_+-=[]{}|;\':"😎,.<>?/`~',
);
await database.playerDao.addPlayer(player: specialPlayer);
final fetchedPlayer = await database.playerDao.getPlayerById(
playerId: specialPlayer.id,
);
expect(fetchedPlayer.name, specialPlayer.name);
},
);
});
group('READ', () {
test('getPlayerCount() works correctly', () async {
var playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 0);
await database.playerDao.addPlayer(player: testPlayer1);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 1);
await database.playerDao.addPlayer(player: testPlayer2);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 2);
await database.playerDao.deletePlayer(playerId: testPlayer1.id);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 1);
await database.playerDao.deletePlayer(playerId: testPlayer2.id);
playerCount = await database.playerDao.getPlayerCount();
expect(playerCount, 0);
});
test('playerExists() works correctly', () async {
var playerExists = await database.playerDao.playerExists(
playerId: testPlayer1.id,
);
expect(playerExists, isFalse);
await database.playerDao.addPlayer(player: testPlayer1);
playerExists = await database.playerDao.playerExists(
playerId: testPlayer1.id,
);
expect(playerExists, isTrue);
});
test(
'getAllPlayers() returns empty list when no players exist',
() async {
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers, isEmpty);
},
);
test('getPlayerById() returns correct player', () async {
await database.playerDao.addPlayer(player: testPlayer1);
await database.playerDao.addPlayer(player: testPlayer2);
final fetchedPlayer = await database.playerDao.getPlayerById( final fetchedPlayer = await database.playerDao.getPlayerById(
playerId: specialPlayer.id, playerId: testPlayer1.id,
); );
expect(fetchedPlayer.name, specialPlayer.name);
},
);
// Verifies that a player with description is stored correctly. expect(fetchedPlayer.id, testPlayer1.id);
test('Player with description is stored correctly', () async { expect(fetchedPlayer.name, testPlayer1.name);
final playerWithDescription = Player( expect(fetchedPlayer.createdAt, testPlayer1.createdAt);
name: 'Described Player', expect(fetchedPlayer.description, testPlayer1.description);
description: 'This is a test description', });
test(
'getPlayerById() throws exception for non-existent player',
() async {
expect(
() => database.playerDao.getPlayerById(playerId: 'non-existent-id'),
throwsA(isA<StateError>()),
);
},
); );
await database.playerDao.addPlayer(player: playerWithDescription);
final fetchedPlayer = await database.playerDao.getPlayerById(
playerId: playerWithDescription.id,
);
expect(fetchedPlayer.name, playerWithDescription.name);
expect(fetchedPlayer.description, playerWithDescription.description);
}); });
// Verifies that a player with null description is stored correctly. group('UPDATE', () {
test('Player with null description is stored correctly', () async { test('updatePlayerName() works correctly', () async {
final playerWithoutDescription = Player( await database.playerDao.addPlayer(player: testPlayer1);
name: 'No Description Player',
description: '', const newName = 'New name';
await database.playerDao.updatePlayerName(
playerId: testPlayer1.id,
name: newName,
);
final player = await database.playerDao.getPlayerById(
playerId: testPlayer1.id,
);
expect(player.name, newName);
});
test('updatePlayerName() does nothing for non-existent player', () async {
final updated = await database.playerDao.updatePlayerName(
playerId: 'non-existent-id',
name: 'New name',
);
expect(updated, isFalse);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers, isEmpty);
});
test('updatePlayerDescription() works correctly', () async {
await database.playerDao.addPlayer(player: testPlayer1);
const newDescription = 'New description';
final updated = await database.playerDao.updatePlayerDescription(
playerId: testPlayer1.id,
description: newDescription,
);
expect(updated, isTrue);
final player = await database.playerDao.getPlayerById(
playerId: testPlayer1.id,
);
expect(player.description, newDescription);
});
test(
'updatePlayerDescription() does nothing for non-existent player',
() async {
final updated = await database.playerDao.updatePlayerDescription(
playerId: 'non-existent-id',
description: 'New description',
);
expect(updated, isFalse);
final allPlayers = await database.playerDao.getAllPlayers();
expect(allPlayers, isEmpty);
},
); );
await database.playerDao.addPlayer(player: playerWithoutDescription); test('Multiple updates to the same player work correctly', () async {
await database.playerDao.addPlayer(player: testPlayer1);
final fetchedPlayer = await database.playerDao.getPlayerById( await database.playerDao.updatePlayerName(
playerId: playerWithoutDescription.id, playerId: testPlayer1.id,
); name: 'First Update',
expect(fetchedPlayer.description, ''); );
var fetchedPlayer = await database.playerDao.getPlayerById(
playerId: testPlayer1.id,
);
expect(fetchedPlayer.name, 'First Update');
await database.playerDao.updatePlayerName(
playerId: testPlayer1.id,
name: 'Second Update',
);
fetchedPlayer = await database.playerDao.getPlayerById(
playerId: testPlayer1.id,
);
expect(fetchedPlayer.name, 'Second Update');
await database.playerDao.updatePlayerDescription(
playerId: testPlayer1.id,
description: 'Third Update',
);
fetchedPlayer = await database.playerDao.getPlayerById(
playerId: testPlayer1.id,
);
expect(fetchedPlayer.description, 'Third Update');
});
}); });
// Verifies that multiple updates to the same player work correctly. group('DELETE', () {
test('Multiple updates to the same player work correctly', () async { test('deletePlayer() works correctly', () async {
await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer1);
final playerDeleted = await database.playerDao.deletePlayer(
playerId: testPlayer1.id,
);
expect(playerDeleted, isTrue);
await database.playerDao.updatePlayerName( final playerExists = await database.playerDao.playerExists(
playerId: testPlayer1.id, playerId: testPlayer1.id,
newName: 'First Update', );
); expect(playerExists, isFalse);
});
var fetchedPlayer = await database.playerDao.getPlayerById( test('deletePlayer() returns false for non-existent player', () async {
playerId: testPlayer1.id, final deleted = await database.playerDao.deletePlayer(
); playerId: 'non-existent-id',
expect(fetchedPlayer.name, 'First Update'); );
expect(deleted, isFalse);
});
await database.playerDao.updatePlayerName( test('deleteAllPlayers() removes all players', () async {
playerId: testPlayer1.id, await database.playerDao.addPlayersAsList(
newName: 'Second Update', players: [testPlayer1, testPlayer2, testPlayer3],
); );
fetchedPlayer = await database.playerDao.getPlayerById( var playerCount = await database.playerDao.getPlayerCount();
playerId: testPlayer1.id, expect(playerCount, 3);
);
expect(fetchedPlayer.name, 'Second Update');
await database.playerDao.updatePlayerName( final deleted = await database.playerDao.deleteAllPlayers();
playerId: testPlayer1.id, expect(deleted, isTrue);
newName: 'Third Update',
);
fetchedPlayer = await database.playerDao.getPlayerById( playerCount = await database.playerDao.getPlayerCount();
playerId: testPlayer1.id, expect(playerCount, 0);
); });
expect(fetchedPlayer.name, 'Third Update');
test('deleteAllPlayers() returns false when no players exist', () async {
final deleted = await database.playerDao.deleteAllPlayers();
expect(deleted, isFalse);
});
}); });
// Verifies that a player with empty string name is stored correctly. group('NAME COUNT', () {
test('Player with empty string name is stored correctly', () async { test('Single player gets initialized wih name count 0', () async {
final emptyNamePlayer = Player(name: '');
await database.playerDao.addPlayer(player: emptyNamePlayer);
final fetchedPlayer = await database.playerDao.getPlayerById(
playerId: emptyNamePlayer.id,
);
expect(fetchedPlayer.name, '');
});
// Verifies that a player with very long name is stored correctly.
test('Player with very long name is stored correctly', () async {
final longName = 'A' * 1000;
final longNamePlayer = Player(name: longName);
await database.playerDao.addPlayer(player: longNamePlayer);
final fetchedPlayer = await database.playerDao.getPlayerById(
playerId: longNamePlayer.id,
);
expect(fetchedPlayer.name, longName);
});
// Verifies that addPlayer returns true on first add.
test('addPlayer returns true when player is added successfully', () async {
final result = await database.playerDao.addPlayer(player: testPlayer1);
expect(result, true);
final playerExists = await database.playerDao.playerExists(
playerId: testPlayer1.id,
);
expect(playerExists, true);
});
group('Name Count Tests', () {
test('Single player gets initialized wih name count 0', () async {
await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer1);
final player = await database.playerDao.getPlayerById( final player = await database.playerDao.getPlayerById(
@@ -392,7 +351,7 @@ void main() {
expect(player.nameCount, 0); expect(player.nameCount, 0);
}); });
test('Multiple players get initialized wih name count 0', () async { test('Multiple players get initialized wih name count 0', () async {
await database.playerDao.addPlayersAsList( await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer2], players: [testPlayer1, testPlayer2],
); );
@@ -470,7 +429,7 @@ void main() {
playerId: testPlayer1.id, playerId: testPlayer1.id,
nameCount: 2, nameCount: 2,
); );
expect(success, true); expect(success, isTrue);
final player = await database.playerDao.getPlayerById( final player = await database.playerDao.getPlayerById(
playerId: testPlayer1.id, playerId: testPlayer1.id,

View File

@@ -42,189 +42,162 @@ void main() {
}); });
group('Player-Group Tests', () { group('Player-Group Tests', () {
// Verifies that a player can be added to an existing group and isPlayerInGroup returns true. group('CREATE', () {
test('Adding a player to a group works correctly', () async { test('addPlayerToGroup() works correctly', () async {
await database.groupDao.addGroup(group: testGroup);
await database.playerDao.addPlayer(player: testPlayer4);
await database.playerGroupDao.addPlayerToGroup(
groupId: testGroup.id,
player: testPlayer4,
);
var playerAdded = await database.playerGroupDao.isPlayerInGroup(
groupId: testGroup.id,
playerId: testPlayer4.id,
);
expect(playerAdded, true);
playerAdded = await database.playerGroupDao.isPlayerInGroup(
groupId: testGroup.id,
playerId: '',
);
expect(playerAdded, false);
});
// Verifies that a player can be removed from a group and the group's member count decreases.
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);
});
// Verifies that getPlayersOfGroup returns all members of a group with correct data.
test('Retrieving players of a group works correctly', () async {
await database.groupDao.addGroup(group: testGroup);
final players = await database.playerGroupDao.getPlayersOfGroup(
groupId: testGroup.id,
);
for (int i = 0; i < players.length; i++) {
expect(players[i].id, testGroup.members[i].id);
expect(players[i].name, testGroup.members[i].name);
expect(players[i].createdAt, testGroup.members[i].createdAt);
}
});
// Verifies that isPlayerInGroup returns false for non-existent player.
test('isPlayerInGroup returns false for non-existent player', () async {
await database.groupDao.addGroup(group: testGroup);
final result = await database.playerGroupDao.isPlayerInGroup(
playerId: 'non-existent-player-id',
groupId: testGroup.id,
);
expect(result, false);
});
// Verifies that isPlayerInGroup returns false for non-existent group.
test('isPlayerInGroup returns false for non-existent group', () async {
await database.playerDao.addPlayer(player: testPlayer1);
final result = await database.playerGroupDao.isPlayerInGroup(
playerId: testPlayer1.id,
groupId: 'non-existent-group-id',
);
expect(result, false);
});
// Verifies that addPlayerToGroup returns false when player already in group.
test(
'addPlayerToGroup returns false when player already in group',
() async {
await database.groupDao.addGroup(group: testGroup); await database.groupDao.addGroup(group: testGroup);
await database.playerDao.addPlayer(player: testPlayer4);
// testPlayer1 is already in testGroup via group creation
final result = await database.playerGroupDao.addPlayerToGroup(
player: testPlayer1,
groupId: testGroup.id,
);
expect(result, false);
},
);
// Verifies that addPlayerToGroup adds player to player table if not exists.
test(
'addPlayerToGroup adds player to player table if not exists',
() async {
await database.groupDao.addGroup(group: testGroup);
// testPlayer4 is not in the database yet
var playerExists = await database.playerDao.playerExists(
playerId: testPlayer4.id,
);
expect(playerExists, false);
await database.playerGroupDao.addPlayerToGroup( await database.playerGroupDao.addPlayerToGroup(
player: testPlayer4,
groupId: testGroup.id, groupId: testGroup.id,
player: testPlayer4,
); );
// Now player should exist in player table var playerAdded = await database.playerGroupDao.isPlayerInGroup(
playerExists = await database.playerDao.playerExists( groupId: testGroup.id,
playerId: testPlayer4.id, playerId: testPlayer4.id,
); );
expect(playerExists, true);
},
);
// Verifies that removePlayerFromGroup returns false for non-existent player. expect(playerAdded, isTrue);
test( });
'removePlayerFromGroup returns false for non-existent player',
() async { test(
'addPlayerToGroup() returns false when player already in group',
() async {
await database.groupDao.addGroup(group: testGroup);
final added = await database.playerGroupDao.addPlayerToGroup(
player: testPlayer1,
groupId: testGroup.id,
);
expect(added, isFalse);
},
);
test(
'addPlayerToGroup() adds player to player table if not exists',
() async {
await database.groupDao.addGroup(group: testGroup);
var playerExists = await database.playerDao.playerExists(
playerId: testPlayer4.id,
);
expect(playerExists, isFalse);
await database.playerGroupDao.addPlayerToGroup(
player: testPlayer4,
groupId: testGroup.id,
);
playerExists = await database.playerDao.playerExists(
playerId: testPlayer4.id,
);
expect(playerExists, isTrue);
},
);
});
group('READ', () {
test(
'isPlayerInGroup() returns false for non-existent player or group',
() async {
await database.groupDao.addGroup(group: testGroup);
var isInGroup = await database.playerGroupDao.isPlayerInGroup(
playerId: 'non-existent-player-id',
groupId: testGroup.id,
);
expect(isInGroup, isFalse);
isInGroup = await database.playerGroupDao.isPlayerInGroup(
playerId: testPlayer1.id,
groupId: 'non-existent-group-id',
);
expect(isInGroup, isFalse);
isInGroup = await database.playerGroupDao.isPlayerInGroup(
playerId: 'non-existent-player-id',
groupId: 'non-existent-group-id',
);
expect(isInGroup, isFalse);
},
);
test('getPlayersOfGroup() works correctly', () async {
await database.groupDao.addGroup(group: testGroup); await database.groupDao.addGroup(group: testGroup);
final players = await database.playerGroupDao.getPlayersOfGroup(
final result = await database.playerGroupDao.removePlayerFromGroup(
playerId: 'non-existent-player-id',
groupId: testGroup.id, groupId: testGroup.id,
); );
expect(result, false); for (int i = 0; i < players.length; i++) {
}, expect(players[i].id, testGroup.members[i].id);
); expect(players[i].name, testGroup.members[i].name);
expect(players[i].createdAt, testGroup.members[i].createdAt);
}
});
// Verifies that removePlayerFromGroup returns false for non-existent group. test('getPlayersOfGroup() returns empty list for empty group', () async {
test( final emptyGroup = Group(name: 'Empty Group', members: []);
'removePlayerFromGroup returns false for non-existent group', await database.groupDao.addGroup(group: emptyGroup);
() async {
await database.playerDao.addPlayer(player: testPlayer1);
final result = await database.playerGroupDao.removePlayerFromGroup( final players = await database.playerGroupDao.getPlayersOfGroup(
playerId: testPlayer1.id, groupId: emptyGroup.id,
groupId: 'non-existent-group-id',
); );
expect(players, isEmpty);
});
expect(result, false); test(
}, 'getPlayersOfGroup() returns empty list for non-existent group',
); () async {
final players = await database.playerGroupDao.getPlayersOfGroup(
// Verifies that getPlayersOfGroup returns empty list for group with no members. groupId: 'non-existent-group-id',
test('getPlayersOfGroup returns empty list for empty group', () async { );
final emptyGroup = Group( expect(players, isEmpty);
name: 'Empty Group', },
description: '',
members: [],
); );
await database.groupDao.addGroup(group: emptyGroup); });
group('UPDATE', () {
test('replaceGroupPlayers() works correctly ', () async {
await database.groupDao.addGroup(group: testGroup);
final players = await database.playerGroupDao.getPlayersOfGroup( var groupMembers = await database.groupDao.getGroupById(
groupId: emptyGroup.id, groupId: testGroup.id,
); );
expect(groupMembers.members.length, testGroup.members.length);
expect(players, isEmpty); final newPlayersList = [testPlayer3, testPlayer4];
final replaced = await database.playerGroupDao.replaceGroupPlayers(
groupId: testGroup.id,
newPlayers: newPlayersList,
);
expect(replaced, isTrue);
groupMembers = await database.groupDao.getGroupById(
groupId: testGroup.id,
);
expect(groupMembers.members.length, 2);
expect(groupMembers.members.any((p) => p.id == testPlayer3.id), isTrue);
expect(groupMembers.members.any((p) => p.id == testPlayer4.id), isTrue);
});
});
group('DELETE', () {
test('removePlayerFromGroup() works correctly', () async {
await database.groupDao.addGroup(group: testGroup);
final removed = await database.playerGroupDao.removePlayerFromGroup(
playerId: testPlayer1.id,
groupId: testGroup.id,
);
expect(removed, isTrue);
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 == testPlayer1.id);
expect(playerExists, isFalse);
});
}); });
// Verifies that getPlayersOfGroup returns empty list for non-existent group.
test(
'getPlayersOfGroup returns empty list for non-existent group',
() async {
final players = await database.playerGroupDao.getPlayersOfGroup(
groupId: 'non-existent-group-id',
);
expect(players, isEmpty);
},
);
// Verifies that removing all players from a group leaves the group empty.
test('Removing all players from a group leaves group empty', () async { test('Removing all players from a group leaves group empty', () async {
await database.groupDao.addGroup(group: testGroup); await database.groupDao.addGroup(group: testGroup);
@@ -240,137 +213,53 @@ void main() {
); );
expect(players, isEmpty); expect(players, isEmpty);
// Group should still exist
final groupExists = await database.groupDao.groupExists( final groupExists = await database.groupDao.groupExists(
groupId: testGroup.id, groupId: testGroup.id,
); );
expect(groupExists, true); expect(groupExists, isTrue);
}); });
// Verifies that a player can be in multiple groups. test('removePlayerFromGroup() works correctly', () async {
test('Player can be in multiple groups', () async {
final secondGroup = Group(
name: 'Second Group',
description: '',
members: [],
);
await database.groupDao.addGroup(group: testGroup); await database.groupDao.addGroup(group: testGroup);
await database.groupDao.addGroup(group: secondGroup);
// Add testPlayer1 to second group (already in testGroup) var removed = await database.playerGroupDao.removePlayerFromGroup(
await database.playerGroupDao.addPlayerToGroup(
player: testPlayer1,
groupId: secondGroup.id,
);
final inFirstGroup = await database.playerGroupDao.isPlayerInGroup(
playerId: testPlayer1.id, playerId: testPlayer1.id,
groupId: testGroup.id, groupId: testGroup.id,
); );
final inSecondGroup = await database.playerGroupDao.isPlayerInGroup( expect(removed, isTrue);
removed = await database.playerGroupDao.removePlayerFromGroup(
playerId: testPlayer1.id, playerId: testPlayer1.id,
groupId: secondGroup.id,
);
expect(inFirstGroup, true);
expect(inSecondGroup, true);
});
// Verifies that removing player from one group doesn't affect other groups.
test(
'Removing player from one group does not affect other groups',
() async {
final secondGroup = Group(
name: 'Second Group',
description: '',
members: [testPlayer1],
);
await database.groupDao.addGroup(group: testGroup);
await database.groupDao.addGroup(group: secondGroup);
// Remove testPlayer1 from testGroup
await database.playerGroupDao.removePlayerFromGroup(
playerId: testPlayer1.id,
groupId: testGroup.id,
);
final inFirstGroup = await database.playerGroupDao.isPlayerInGroup(
playerId: testPlayer1.id,
groupId: testGroup.id,
);
final inSecondGroup = await database.playerGroupDao.isPlayerInGroup(
playerId: testPlayer1.id,
groupId: secondGroup.id,
);
expect(inFirstGroup, false);
expect(inSecondGroup, true);
},
);
// Verifies that addPlayerToGroup returns true on successful addition.
test('addPlayerToGroup returns true on successful addition', () async {
await database.groupDao.addGroup(group: testGroup);
await database.playerDao.addPlayer(player: testPlayer4);
final result = await database.playerGroupDao.addPlayerToGroup(
player: testPlayer4,
groupId: testGroup.id, groupId: testGroup.id,
); );
expect(removed, isFalse);
expect(result, true);
}); });
// Verifies that removing the same player twice returns false on second attempt.
test( test(
'Removing same player twice returns false on second attempt', 'removePlayerFromGroup() returns false for non-existent player or group',
() async { () async {
await database.groupDao.addGroup(group: testGroup); await database.groupDao.addGroup(group: testGroup);
final firstRemoval = await database.playerGroupDao await database.groupDao.addGroup(group: testGroup);
.removePlayerFromGroup(
playerId: testPlayer1.id,
groupId: testGroup.id,
);
expect(firstRemoval, true);
final secondRemoval = await database.playerGroupDao var removed = await database.playerGroupDao.removePlayerFromGroup(
.removePlayerFromGroup( playerId: 'non-existent-player-id',
playerId: testPlayer1.id, groupId: testGroup.id,
groupId: testGroup.id, );
); expect(removed, isFalse);
expect(secondRemoval, false);
removed = await database.playerGroupDao.removePlayerFromGroup(
playerId: testPlayer1.id,
groupId: 'non-existent-group-id',
);
expect(removed, isFalse);
removed = await database.playerGroupDao.removePlayerFromGroup(
playerId: 'non-existent-player-id',
groupId: 'non-existent-group-id',
);
expect(removed, isFalse);
}, },
); );
// Verifies that replaceGroupPlayers removes all existing players and replaces with new list.
test('replaceGroupPlayers replaces all group members correctly', () async {
// Create initial group with 3 players
await database.groupDao.addGroup(group: testGroup);
// Verify initial members
var groupMembers = await database.groupDao.getGroupById(
groupId: testGroup.id,
);
expect(groupMembers.members.length, 3);
// Replace with new list containing 2 different players
final newPlayersList = [testPlayer3, testPlayer4];
await database.groupDao.replaceGroupPlayers(
groupId: testGroup.id,
newPlayers: newPlayersList,
);
// Get updated group and verify members
groupMembers = await database.groupDao.getGroupById(
groupId: testGroup.id,
);
expect(groupMembers.members.length, 2);
expect(groupMembers.members.any((p) => p.id == testPlayer3.id), true);
expect(groupMembers.members.any((p) => p.id == testPlayer4.id), true);
expect(groupMembers.members.any((p) => p.id == testPlayer1.id), false);
expect(groupMembers.members.any((p) => p.id == testPlayer2.id), false);
});
}); });
} }

View File

@@ -5,7 +5,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:tallee/core/enums.dart'; import 'package:tallee/core/enums.dart';
import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/db/database.dart';
import 'package:tallee/data/models/game.dart'; import 'package:tallee/data/models/game.dart';
import 'package:tallee/data/models/group.dart';
import 'package:tallee/data/models/match.dart'; import 'package:tallee/data/models/match.dart';
import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/team.dart'; import 'package:tallee/data/models/team.dart';
@@ -17,11 +16,8 @@ void main() {
late Player testPlayer3; late Player testPlayer3;
late Player testPlayer4; late Player testPlayer4;
late Player testPlayer5; late Player testPlayer5;
late Player testPlayer6;
late Group testGroup;
late Game testGame; late Game testGame;
late Match testMatchOnlyGroup; late Match testMatch1;
late Match testMatchOnlyPlayers;
late Team testTeam1; late Team testTeam1;
late Team testTeam2; late Team testTeam2;
final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); final fixedDate = DateTime(2025, 11, 19, 00, 11, 23);
@@ -42,12 +38,6 @@ 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');
testPlayer6 = Player(name: 'Frank');
testGroup = Group(
name: 'Test Group',
description: '',
members: [testPlayer1, testPlayer2, testPlayer3],
);
testGame = Game( testGame = Game(
name: 'Test Game', name: 'Test Game',
ruleset: Ruleset.singleWinner, ruleset: Ruleset.singleWinner,
@@ -55,31 +45,17 @@ void main() {
color: GameColor.blue, color: GameColor.blue,
icon: '', icon: '',
); );
testMatchOnlyGroup = Match( testMatch1 = Match(
name: 'Test Match with Group',
game: testGame,
players: testGroup.members,
group: testGroup,
);
testMatchOnlyPlayers = Match(
name: 'Test Match with Players', name: 'Test Match with Players',
game: testGame, game: testGame,
players: [testPlayer4, testPlayer5, testPlayer6], players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
); );
testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]); testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]);
testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]); testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]);
}); });
await database.playerDao.addPlayersAsList( await database.playerDao.addPlayersAsList(
players: [ players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
testPlayer1,
testPlayer2,
testPlayer3,
testPlayer4,
testPlayer5,
testPlayer6,
],
); );
await database.groupDao.addGroup(group: testGroup);
await database.gameDao.addGame(game: testGame); await database.gameDao.addGame(game: testGame);
}); });
tearDown(() async { tearDown(() async {
@@ -87,603 +63,361 @@ void main() {
}); });
group('Player-Match Tests', () { group('Player-Match Tests', () {
test('Match has player works correctly', () async { group('CREATE', () {
await database.matchDao.addMatch(match: testMatchOnlyGroup); test('addPlayerToMatch() works correctly', () async {
await database.playerDao.addPlayer(player: testPlayer1); await database.matchDao.addMatch(match: testMatch1);
await database.playerDao.addPlayer(player: testPlayer1);
var matchHasPlayers = await database.playerMatchDao.matchHasPlayers( var added = await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id, matchId: testMatch1.id,
); playerId: testPlayer1.id,
expect(matchHasPlayers, true);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
);
matchHasPlayers = await database.playerMatchDao.matchHasPlayers(
matchId: testMatchOnlyGroup.id,
);
expect(matchHasPlayers, true);
});
test('Adding a player to a match works correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
await database.playerDao.addPlayer(player: testPlayer5);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer5.id,
);
var playerAdded = await database.playerMatchDao.isPlayerInMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer5.id,
);
expect(playerAdded, true);
playerAdded = await database.playerMatchDao.isPlayerInMatch(
matchId: testMatchOnlyGroup.id,
playerId: '',
);
expect(playerAdded, false);
});
test('Removing player from match works correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
final playerToRemove = testMatchOnlyPlayers.players[0];
final removed = await database.playerMatchDao.removePlayerFromMatch(
playerId: playerToRemove.id,
matchId: testMatchOnlyPlayers.id,
);
expect(removed, true);
final result = await database.matchDao.getMatchById(
matchId: testMatchOnlyPlayers.id,
);
expect(result.players.length, testMatchOnlyPlayers.players.length - 1);
final playerExists = result.players.any((p) => p.id == playerToRemove.id);
expect(playerExists, false);
});
test('Retrieving players of a match works correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
final players =
await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatchOnlyPlayers.id,
) ??
[];
for (int i = 0; i < players.length; i++) {
expect(players[i].id, testMatchOnlyPlayers.players[i].id);
expect(players[i].name, testMatchOnlyPlayers.players[i].name);
expect(players[i].createdAt, testMatchOnlyPlayers.players[i].createdAt);
}
});
test('Updating the match players works correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
final newPlayers = [testPlayer1, testPlayer2, testPlayer4];
await database.playerDao.addPlayersAsList(players: newPlayers);
// First, remove all existing players
final existingPlayers = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatchOnlyPlayers.id,
);
if (existingPlayers == null || existingPlayers.isEmpty) {
fail('Existing players should not be null or empty');
}
await database.playerMatchDao.updatePlayersFromMatch(
matchId: testMatchOnlyPlayers.id,
newPlayer: newPlayers,
);
final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatchOnlyPlayers.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);
}
});
test(
'Adding the same player to separate matches works correctly',
() async {
final playersList = [testPlayer1, testPlayer2, testPlayer3];
final match1 = Match(
name: 'Match 1',
game: testGame,
players: playersList,
notes: '',
); );
final match2 = Match( expect(added, isTrue);
name: 'Match 2',
game: testGame, added = await database.playerMatchDao.isPlayerInMatch(
players: playersList, matchId: testMatch1.id,
notes: '', playerId: testPlayer1.id,
); );
expect(added, isTrue);
});
await Future.wait([ test('addPlayerToMatch() with team works correctly', () async {
database.matchDao.addMatch(match: match1), await database.matchDao.addMatch(match: testMatch1);
database.matchDao.addMatch(match: match2), await database.teamDao.addTeam(team: testTeam1, matchId: testMatch1.id);
]);
final players1 = await database.playerMatchDao.getPlayersOfMatch( await database.playerMatchDao.addPlayerToMatch(
matchId: match1.id, matchId: testMatch1.id,
); playerId: testPlayer3.id,
final players2 = await database.playerMatchDao.getPlayersOfMatch(
matchId: match2.id,
);
expect(players1, isNotNull);
expect(players2, isNotNull);
expect(
players1!.map((p) => p.id).toList(),
equals(players2!.map((p) => p.id).toList()),
);
expect(
players1.map((p) => p.name).toList(),
equals(players2.map((p) => p.name).toList()),
);
expect(
players1.map((p) => p.createdAt).toList(),
equals(players2.map((p) => p.createdAt).toList()),
);
},
);
// Verifies that getPlayersOfMatch returns null for a non-existent match.
test('getPlayersOfMatch returns null for non-existent match', () async {
final players = await database.playerMatchDao.getPlayersOfMatch(
matchId: 'non-existent-match-id',
);
expect(players, isNull);
});
test('Adding player with teamId works correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
await database.teamDao.addTeam(team: testTeam1);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
teamId: testTeam1.id,
);
final playersInTeam = await database.playerMatchDao.getPlayersInTeam(
matchId: testMatchOnlyGroup.id,
teamId: testTeam1.id,
);
expect(playersInTeam.length, 1);
expect(playersInTeam[0].id, testPlayer1.id);
});
test('updatePlayerTeam updates team correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
await database.teamDao.addTeam(team: testTeam1);
await database.teamDao.addTeam(team: testTeam2);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
teamId: testTeam1.id,
);
// Update player's team
final updated = await database.playerMatchDao.updatePlayerTeam(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
teamId: testTeam2.id,
);
expect(updated, true);
// Verify player is now in testTeam2
final playersInTeam2 = await database.playerMatchDao.getPlayersInTeam(
matchId: testMatchOnlyGroup.id,
teamId: testTeam2.id,
);
expect(playersInTeam2.length, 1);
expect(playersInTeam2[0].id, testPlayer1.id);
// Verify player is no longer in testTeam1
final playersInTeam1 = await database.playerMatchDao.getPlayersInTeam(
matchId: testMatchOnlyGroup.id,
teamId: testTeam1.id,
);
expect(playersInTeam1.isEmpty, true);
});
test('updatePlayerTeam can remove player from team', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
await database.teamDao.addTeam(team: testTeam1);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
teamId: testTeam1.id,
);
// Remove player from team by setting teamId to null
final updated = await database.playerMatchDao.updatePlayerTeam(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
teamId: null,
);
expect(updated, true);
final playersInTeam = await database.playerMatchDao.getPlayersInTeam(
matchId: testMatchOnlyGroup.id,
teamId: testTeam1.id,
);
expect(playersInTeam.isEmpty, true);
});
test(
'updatePlayerTeam returns false for non-existent player-match',
() async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
final updated = await database.playerMatchDao.updatePlayerTeam(
matchId: testMatchOnlyGroup.id,
playerId: 'non-existent-player-id',
teamId: testTeam1.id, teamId: testTeam1.id,
); );
expect(updated, false); final playersInTeam = await database.playerMatchDao
}, .getPlayersOfTeamInMatch(
); matchId: testMatch1.id,
teamId: testTeam1.id,
);
// Verifies that getPlayersInTeam returns empty list for non-existent team. expect(playersInTeam, isNotEmpty);
test('getPlayersInTeam returns empty list for non-existent team', () async { expect(playersInTeam.length, 3);
await database.matchDao.addMatch(match: testMatchOnlyPlayers); });
final players = await database.playerMatchDao.getPlayersInTeam( test('addPlayerToMatch() ignores duplicates', () async {
matchId: testMatchOnlyPlayers.id, await database.matchDao.addMatch(match: testMatch1);
teamId: 'non-existent-team-id', await database.playerDao.addPlayer(player: testPlayer5);
);
expect(players.isEmpty, true); final isInMatch = await database.playerMatchDao.isPlayerInMatch(
matchId: testMatch1.id,
playerId: testPlayer5.id,
);
expect(isInMatch, isFalse);
var players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatch1.id,
);
expect(players.length, testMatch1.players.length);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatch1.id,
playerId: testPlayer5.id,
);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatch1.id,
playerId: testPlayer5.id,
);
players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatch1.id,
);
expect(players.length, testMatch1.players.length + 1);
});
}); });
test('getPlayersInTeam returns all players of a team', () async { group('READ', () {
await database.matchDao.addMatch(match: testMatchOnlyGroup); test('hasMatchPlayers() works correctly', () async {
await database.teamDao.addTeam(team: testTeam1); await database.matchDao.addMatch(match: testMatch1);
var matchHasPlayers = await database.playerMatchDao.hasMatchPlayers(
matchId: testMatch1.id,
);
expect(matchHasPlayers, isTrue);
});
await database.playerMatchDao.addPlayerToMatch( test('hasMatchPlayers() returns false for non-existent match', () async {
matchId: testMatchOnlyGroup.id, final hasPlayers = await database.playerMatchDao.hasMatchPlayers(
playerId: testPlayer1.id, matchId: 'non-existent-match-id',
teamId: testTeam1.id, );
); expect(hasPlayers, isFalse);
await database.playerMatchDao.addPlayerToMatch( });
matchId: testMatchOnlyGroup.id,
playerId: testPlayer2.id, test('isPlayerInMatch() works correctly', () async {
teamId: testTeam1.id, await database.matchDao.addMatch(match: testMatch1);
final isInMatch = await database.playerMatchDao.isPlayerInMatch(
matchId: testMatch1.id,
playerId: testPlayer1.id,
);
expect(isInMatch, isTrue);
});
test('isPlayerInMatch() returns false for non-existent match', () async {
final isInMatch = await database.playerMatchDao.isPlayerInMatch(
matchId: 'non-existent-match-id',
playerId: testPlayer1.id,
);
expect(isInMatch, isFalse);
});
test('getPlayersOfMatch() works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
final players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatch1.id,
);
expect(players, isNotEmpty);
for (int i = 0; i < players.length; i++) {
expect(players[i].id, testMatch1.players[i].id);
expect(players[i].name, testMatch1.players[i].name);
expect(players[i].createdAt, testMatch1.players[i].createdAt);
}
});
test(
'getPlayersOfMatch() returns empty list for non-existent match',
() async {
final players = await database.playerMatchDao.getPlayersOfMatch(
matchId: 'non-existent-match-id',
);
expect(players, isEmpty);
},
); );
final playersInTeam = await database.playerMatchDao.getPlayersInTeam( test('getPlayersInTeam() works correctly', () async {
matchId: testMatchOnlyGroup.id, // Create a match with teams
teamId: testTeam1.id, final matchWithTeams = Match(
name: 'Match with teams',
game: testGame,
players: [],
teams: [testTeam1, testTeam2],
);
await database.matchDao.addMatch(match: matchWithTeams);
var playersInTeam = await database.playerMatchDao
.getPlayersOfTeamInMatch(
matchId: matchWithTeams.id,
teamId: testTeam1.id,
);
expect(playersInTeam, isNotEmpty);
expect(playersInTeam.length, 2);
var playerIds = playersInTeam.map((p) => p.id).toSet();
expect(playerIds.contains(testPlayer1.id), isTrue);
expect(playerIds.contains(testPlayer2.id), isTrue);
playersInTeam = await database.playerMatchDao.getPlayersOfTeamInMatch(
matchId: matchWithTeams.id,
teamId: testTeam2.id,
);
expect(playersInTeam, isNotEmpty);
expect(playersInTeam.length, 2);
playerIds = playersInTeam.map((p) => p.id).toSet();
expect(playerIds.contains(testPlayer3.id), isTrue);
expect(playerIds.contains(testPlayer4.id), isTrue);
});
test(
'getPlayersInTeam() returns empty list for non-existent match',
() async {
final players = await database.playerMatchDao.getPlayersOfTeamInMatch(
matchId: 'non-existent-match-id',
teamId: testTeam1.id,
);
expect(players, isEmpty);
},
); );
expect(playersInTeam.length, 2);
final playerIds = playersInTeam.map((p) => p.id).toSet();
expect(playerIds.contains(testPlayer1.id), true);
expect(playerIds.contains(testPlayer2.id), true);
}); });
test( group('UPDATE', () {
'removePlayerFromMatch returns false for non-existent player', test('updateMatchPlayers() works correctly', () async {
() async { await database.matchDao.addMatch(match: testMatch1);
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
final newPlayers = [testPlayer1, testPlayer2, testPlayer4];
await database.playerDao.addPlayersAsList(players: newPlayers);
final existingPlayers = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatch1.id,
);
expect(existingPlayers, isNotEmpty);
await database.playerMatchDao.updateMatchPlayers(
matchId: testMatch1.id,
player: newPlayers,
);
final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatch1.id,
);
expect(updatedPlayers, isNotEmpty);
expect(updatedPlayers.length, newPlayers.length);
/// Create a map of new players for easy lookup
final testPlayers = {for (var p in newPlayers) p.id: p};
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);
}
});
test('updatePlayersTeam() works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
await database.teamDao.addTeam(team: testTeam1, matchId: testMatch1.id);
await database.teamDao.addTeam(team: testTeam2, matchId: testMatch1.id);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatch1.id,
playerId: testPlayer1.id,
teamId: testTeam1.id,
);
final updated = await database.playerMatchDao.updatePlayersTeam(
matchId: testMatch1.id,
playerId: testPlayer1.id,
teamId: testTeam2.id,
);
expect(updated, isTrue);
final playersInTeam2 = await database.playerMatchDao
.getPlayersOfTeamInMatch(
matchId: testMatch1.id,
teamId: testTeam2.id,
);
expect(playersInTeam2, isNotEmpty);
expect(playersInTeam2.length, 3);
final playersInTeam1 = await database.playerMatchDao
.getPlayersOfTeamInMatch(
matchId: testMatch1.id,
teamId: testTeam1.id,
);
expect(playersInTeam1, isNotEmpty);
expect(playersInTeam1.length, 1);
expect(playersInTeam1[0].id, testPlayer2.id);
});
test(
'updatePlayersTeam() returns false for non-existent player-match',
() async {
await database.matchDao.addMatch(match: testMatch1);
final updated = await database.playerMatchDao.updatePlayersTeam(
matchId: testMatch1.id,
playerId: 'non-existent-player-id',
teamId: testTeam1.id,
);
expect(updated, isFalse);
},
);
test('updateMatchPlayers() works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
var matchPlayers = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(matchPlayers.players.length, testMatch1.players.length);
final newPlayersList = [testPlayer1, testPlayer2];
await database.playerMatchDao.updateMatchPlayers(
matchId: testMatch1.id,
player: newPlayersList,
);
matchPlayers = await database.matchDao.getMatchById(
matchId: testMatch1.id,
);
expect(matchPlayers.players.length, 2);
expect(matchPlayers.players.any((p) => p.id == testPlayer1.id), isTrue);
expect(matchPlayers.players.any((p) => p.id == testPlayer2.id), isTrue);
});
test('updateMatchPlayers() with same players makes is ignored', () async {
await database.matchDao.addMatch(match: testMatch1);
final originalPlayers = testMatch1.players;
final updated = await database.playerMatchDao.updateMatchPlayers(
matchId: testMatch1.id,
player: originalPlayers,
);
expect(updated, isFalse);
final players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatch1.id,
);
expect(players.length, originalPlayers.length);
final playerIds = players.map((p) => p.id).toSet();
for (final originalPlayer in originalPlayers) {
expect(playerIds.contains(originalPlayer.id), isTrue);
}
});
test('updateMatchPlayers() with empty list returns false', () async {
await database.matchDao.addMatch(match: testMatch1);
final updated = await database.playerMatchDao.updateMatchPlayers(
matchId: testMatch1.id,
player: [],
);
expect(updated, isFalse);
});
});
group('DELETE', () {
test('removePlayerFromMatch() works correctly', () async {
await database.matchDao.addMatch(match: testMatch1);
final playerToRemove = testMatch1.players[0];
final removed = await database.playerMatchDao.removePlayerFromMatch( final removed = await database.playerMatchDao.removePlayerFromMatch(
playerId: 'non-existent-player-id', playerId: playerToRemove.id,
matchId: testMatchOnlyPlayers.id, matchId: testMatch1.id,
); );
expect(removed, isTrue);
expect(removed, false); final result = await database.matchDao.getMatchById(
}, matchId: testMatch1.id,
);
test('Adding same player twice to same match is ignored', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
);
final players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatchOnlyGroup.id,
);
expect(players?.length, 3);
});
test(
'updatePlayersFromMatch with empty list removes all players',
() async {
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
// Verify players exist initially
var players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatchOnlyPlayers.id,
); );
expect(players?.length, 3); expect(result.players.length, testMatch1.players.length - 1);
// Update with empty list final playerExists = result.players.any(
await database.playerMatchDao.updatePlayersFromMatch( (p) => p.id == playerToRemove.id,
matchId: testMatchOnlyPlayers.id,
newPlayer: [],
); );
expect(playerExists, isFalse);
});
// Verify all players are removed test(
players = await database.playerMatchDao.getPlayersOfMatch( 'removePlayerFromMatch() returns false for non-existent player',
matchId: testMatchOnlyPlayers.id, () async {
); await database.matchDao.addMatch(match: testMatch1);
expect(players, isNull);
},
);
test('updatePlayersFromMatch with same players makes no changes', () async { final removed = await database.playerMatchDao.removePlayerFromMatch(
await database.matchDao.addMatch(match: testMatchOnlyPlayers); playerId: 'non-existent-player-id',
matchId: testMatch1.id,
);
final originalPlayers = [testPlayer4, testPlayer5, testPlayer6]; expect(removed, isFalse);
},
await database.playerMatchDao.updatePlayersFromMatch(
matchId: testMatchOnlyPlayers.id,
newPlayer: originalPlayers,
); );
final players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatchOnlyPlayers.id,
);
expect(players?.length, originalPlayers.length);
final playerIds = players!.map((p) => p.id).toSet();
for (final originalPlayer in originalPlayers) {
expect(playerIds.contains(originalPlayer.id), true);
}
});
test('matchHasPlayers returns false for non-existent match', () async {
final hasPlayers = await database.playerMatchDao.matchHasPlayers(
matchId: 'non-existent-match-id',
);
expect(hasPlayers, false);
});
test('isPlayerInMatch returns false for non-existent match', () async {
final isInMatch = await database.playerMatchDao.isPlayerInMatch(
matchId: 'non-existent-match-id',
playerId: testPlayer1.id,
);
expect(isInMatch, false);
});
// Verifies that getPlayersInTeam returns empty list for non-existent match.
test(
'getPlayersInTeam returns empty list for non-existent match',
() async {
await database.teamDao.addTeam(team: testTeam1);
final players = await database.playerMatchDao.getPlayersInTeam(
matchId: 'non-existent-match-id',
teamId: testTeam1.id,
);
expect(players.isEmpty, true);
},
);
// Verifies that players in different teams within the same match are returned correctly.
test('Players in different teams within same match are separate', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
await database.teamDao.addTeam(team: testTeam1);
await database.teamDao.addTeam(team: testTeam2);
// Add players to different teams
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
teamId: testTeam1.id,
);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer2.id,
teamId: testTeam1.id,
);
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer3.id,
teamId: testTeam2.id,
);
// Verify team 1 players
final playersInTeam1 = await database.playerMatchDao.getPlayersInTeam(
matchId: testMatchOnlyGroup.id,
teamId: testTeam1.id,
);
expect(playersInTeam1.length, 2);
final team1Ids = playersInTeam1.map((p) => p.id).toSet();
expect(team1Ids.contains(testPlayer1.id), true);
expect(team1Ids.contains(testPlayer2.id), true);
expect(team1Ids.contains(testPlayer3.id), false);
// Verify team 2 players
final playersInTeam2 = await database.playerMatchDao.getPlayersInTeam(
matchId: testMatchOnlyGroup.id,
teamId: testTeam2.id,
);
expect(playersInTeam2.length, 1);
expect(playersInTeam2[0].id, testPlayer3.id);
});
// Verifies that removePlayerFromMatch does not affect other matches.
test('removePlayerFromMatch does not affect other matches', () async {
final playersList = [testPlayer1, testPlayer2];
final match1 = Match(
name: 'Match 1',
game: testGame,
players: playersList,
);
final match2 = Match(
name: 'Match 2',
game: testGame,
players: playersList,
);
await Future.wait([
database.matchDao.addMatch(match: match1),
database.matchDao.addMatch(match: match2),
]);
// Remove player from match1
final removed = await database.playerMatchDao.removePlayerFromMatch(
playerId: testPlayer1.id,
matchId: match1.id,
);
expect(removed, true);
// Verify player is removed from match1
final isInMatch1 = await database.playerMatchDao.isPlayerInMatch(
matchId: match1.id,
playerId: testPlayer1.id,
);
expect(isInMatch1, false);
// Verify player still exists in match2
final isInMatch2 = await database.playerMatchDao.isPlayerInMatch(
matchId: match2.id,
playerId: testPlayer1.id,
);
expect(isInMatch2, true);
});
// Verifies that updatePlayersFromMatch on non-existent match fails with constraint error.
test(
'updatePlayersFromMatch on non-existent match fails with foreign key constraint',
() async {
// Should throw due to foreign key constraint - match doesn't exist
await expectLater(
database.playerMatchDao.updatePlayersFromMatch(
matchId: 'non-existent-match-id',
newPlayer: [testPlayer1, testPlayer2],
),
throwsA(anything),
);
},
);
// Verifies that a player can be in a match without being assigned to a team.
test('Player can exist in match without team assignment', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
await database.teamDao.addTeam(team: testTeam1);
// Add player to match without team
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
);
// Add another player to match with team
await database.playerMatchDao.addPlayerToMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer2.id,
teamId: testTeam1.id,
);
// Verify both players are in the match
final isPlayer1InMatch = await database.playerMatchDao.isPlayerInMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer1.id,
);
final isPlayer2InMatch = await database.playerMatchDao.isPlayerInMatch(
matchId: testMatchOnlyGroup.id,
playerId: testPlayer2.id,
);
expect(isPlayer1InMatch, true);
expect(isPlayer2InMatch, true);
// Verify only player2 is in the team
final playersInTeam = await database.playerMatchDao.getPlayersInTeam(
matchId: testMatchOnlyGroup.id,
teamId: testTeam1.id,
);
expect(playersInTeam.length, 1);
expect(playersInTeam[0].id, testPlayer2.id);
});
// Verifies that replaceMatchPlayers removes all existing players and replaces with new list.
test('replaceMatchPlayers replaces all match players correctly', () async {
// Create initial match with 3 players
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
// Verify initial players
var matchPlayers = await database.matchDao.getMatchById(
matchId: testMatchOnlyPlayers.id,
);
expect(matchPlayers.players.length, 3);
// Replace with new list containing 2 different players
final newPlayersList = [testPlayer1, testPlayer2];
await database.matchDao.replaceMatchPlayers(
matchId: testMatchOnlyPlayers.id,
newPlayers: newPlayersList,
);
// Get updated match and verify players
matchPlayers = await database.matchDao.getMatchById(
matchId: testMatchOnlyPlayers.id,
);
expect(matchPlayers.players.length, 2);
expect(matchPlayers.players.any((p) => p.id == testPlayer1.id), true);
expect(matchPlayers.players.any((p) => p.id == testPlayer2.id), true);
expect(matchPlayers.players.any((p) => p.id == testPlayer4.id), false);
expect(matchPlayers.players.any((p) => p.id == testPlayer5.id), false);
expect(matchPlayers.players.any((p) => p.id == testPlayer6.id), false);
}); });
}); });
} }

View File

@@ -17,6 +17,9 @@ void main() {
late Game testGame; late Game testGame;
late Match testMatch1; late Match testMatch1;
late Match testMatch2; late Match testMatch2;
ScoreEntry entryRound1 = ScoreEntry(roundNumber: 1, score: 10, change: 10);
ScoreEntry entryRound2 = ScoreEntry(roundNumber: 2, score: 25, change: 15);
ScoreEntry entryRound3 = ScoreEntry(roundNumber: 3, score: 30, change: 5);
final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); final fixedDate = DateTime(2025, 11, 19, 00, 11, 23);
final fakeClock = Clock(() => fixedDate); final fakeClock = Clock(() => fixedDate);
@@ -65,13 +68,12 @@ void main() {
}); });
group('Score Tests', () { group('Score Tests', () {
group('Adding and Fetching scores', () { group('CREATE', () {
test('Single Score', () async { test('Adding and fetching single score works correctly', () async {
ScoreEntry entry = ScoreEntry(roundNumber: 1, score: 10, change: 10);
await database.scoreEntryDao.addScore( await database.scoreEntryDao.addScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
entry: entry, entry: entryRound1,
); );
final score = await database.scoreEntryDao.getScore( final score = await database.scoreEntryDao.getScore(
@@ -81,41 +83,37 @@ void main() {
); );
expect(score, isNotNull); expect(score, isNotNull);
expect(score!.roundNumber, 1); expect(score!.roundNumber, entryRound1.roundNumber);
expect(score.score, 10); expect(score.score, entryRound1.score);
expect(score.change, 10); expect(score.change, entryRound1.change);
}); });
test('Multiple Scores', () async { test('Adding and fetching single score works correctly', () async {
final entryList = [
ScoreEntry(roundNumber: 1, score: 5, change: 5),
ScoreEntry(roundNumber: 2, score: 12, change: 7),
ScoreEntry(roundNumber: 3, score: 18, change: 6),
];
await database.scoreEntryDao.addScoresAsList( await database.scoreEntryDao.addScoresAsList(
entrys: entryList, entrys: [entryRound1, entryRound2, entryRound3],
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
); );
final scores = await database.scoreEntryDao.getAllPlayerScoresInMatch( final entrys = await database.scoreEntryDao.getAllPlayerScoresInMatch(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(scores, isNotNull); expect(entrys, isNotEmpty);
// Scores should be returned in order of round number // Map for connecting fetched entry with expected entrys
for (int i = 0; i < entryList.length; i++) { final testScores = {1: entryRound1, 2: entryRound2, 3: entryRound3};
expect(scores[i].roundNumber, entryList[i].roundNumber);
expect(scores[i].score, entryList[i].score); for (final entry in entrys) {
expect(scores[i].change, entryList[i].change); final testEntry = testScores[entry.roundNumber]!;
expect(entry.roundNumber, testEntry.roundNumber);
expect(entry.score, testEntry.score);
expect(entry.change, testEntry.change);
} }
}); });
});
group('Undesirable values', () {
test('Score & Round can have negative values', () async { test('Score & Round can have negative values', () async {
ScoreEntry entry = ScoreEntry(roundNumber: -2, score: -10, change: -10); ScoreEntry entry = ScoreEntry(roundNumber: -2, score: -10, change: -10);
await database.scoreEntryDao.addScore( await database.scoreEntryDao.addScore(
@@ -155,6 +153,31 @@ void main() {
expect(score.change, 0); expect(score.change, 0);
}); });
test('Adding the same score twice replaces the existing one', () async {
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: entryRound1,
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: entryRound1,
);
final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
);
expect(score, isNotNull);
expect(score!.score, entryRound1.score);
expect(score.change, entryRound1.change);
});
});
group('READ', () {
test('Getting score for a non-existent entities returns null', () async { test('Getting score for a non-existent entities returns null', () async {
var score = await database.scoreEntryDao.getScore( var score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
@@ -201,10 +224,8 @@ void main() {
expect(score, isNull); expect(score, isNull);
}); });
});
group('Scores in matches', () { test('getAllMatchScores() works correctly', () async {
test('getAllMatchScores()', () async {
ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10);
ScoreEntry entry2 = ScoreEntry(roundNumber: 1, score: 20, change: 20); ScoreEntry entry2 = ScoreEntry(roundNumber: 1, score: 20, change: 20);
ScoreEntry entry3 = ScoreEntry(roundNumber: 2, score: 25, change: 15); ScoreEntry entry3 = ScoreEntry(roundNumber: 2, score: 25, change: 15);
@@ -238,10 +259,10 @@ void main() {
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(scores.isEmpty, true); expect(scores.isEmpty, isTrue);
}); });
test('getAllPlayerScoresInMatch()', () async { test('getAllPlayerScoresInMatch() works correctly', () async {
ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10);
ScoreEntry entry2 = ScoreEntry(roundNumber: 2, score: 25, change: 15); ScoreEntry entry2 = ScoreEntry(roundNumber: 2, score: 25, change: 15);
ScoreEntry entry3 = ScoreEntry(roundNumber: 1, score: 30, change: 30); ScoreEntry entry3 = ScoreEntry(roundNumber: 1, score: 30, change: 30);
@@ -278,7 +299,7 @@ void main() {
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(playerScores.isEmpty, true); expect(playerScores.isEmpty, isTrue);
}); });
test('Scores are isolated across different matches', () async { test('Scores are isolated across different matches', () async {
@@ -315,31 +336,109 @@ void main() {
expect(match2Scores[0].score, 50); expect(match2Scores[0].score, 50);
expect(match2Scores[0].change, 50); expect(match2Scores[0].change, 50);
}); });
test('getLatestRoundNumber() works correctly', () async {
var latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, isNull);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: entryRound1,
);
latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, 1);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: entryRound2,
);
latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, 2);
});
test('getLatestRoundNumber() with non-consecutive rounds', () async {
await database.scoreEntryDao.addScoresAsList(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entrys: [entryRound1, entryRound3],
);
final latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, 3);
});
test('getTotalScoreForPlayer() works correctly', () async {
var totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(totalScore, 0);
await database.scoreEntryDao.addScoresAsList(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entrys: [entryRound1, entryRound2, entryRound3],
);
totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
final expectedTotal =
entryRound1.change + entryRound2.change + entryRound3.change;
expect(totalScore, expectedTotal);
});
test('getTotalScoreForPlayer() ignores round score', () async {
await database.scoreEntryDao.addScoresAsList(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entrys: [
ScoreEntry(roundNumber: 2, score: 25, change: 25),
ScoreEntry(roundNumber: 1, score: 25, change: 10),
ScoreEntry(roundNumber: 3, score: 25, change: 25),
],
);
final totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
// Should return the sum of all changes
expect(totalScore, 60);
});
}); });
group('Updating scores', () { group('UPDATE', () {
test('updateScore()', () async { test('updateScore() works correctly', () async {
ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); await database.scoreEntryDao.addScoresAsList(
ScoreEntry entry2 = ScoreEntry(roundNumber: 2, score: 15, change: 5);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
entry: entry1, entrys: [entryRound1, entryRound2],
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: entry2,
); );
final newEntry = ScoreEntry(roundNumber: 2, score: 50, change: 40);
final updated = await database.scoreEntryDao.updateScore( final updated = await database.scoreEntryDao.updateScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
newEntry: ScoreEntry(roundNumber: 2, score: 50, change: 40), entry: newEntry,
); );
expect(updated, true); expect(updated, isTrue);
final score = await database.scoreEntryDao.getScore( final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
@@ -348,23 +447,23 @@ void main() {
); );
expect(score, isNotNull); expect(score, isNotNull);
expect(score!.score, 50); expect(score!.score, newEntry.score);
expect(score.change, 40); expect(score.change, newEntry.change);
}); });
test('Updating a non-existent score returns false', () async { test('Updating a non-existent score returns false', () async {
final updated = await database.scoreEntryDao.updateScore( final updated = await database.scoreEntryDao.updateScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
newEntry: ScoreEntry(roundNumber: 1, score: 20, change: 20), entry: entryRound1,
); );
expect(updated, false); expect(updated, isFalse);
}); });
}); });
group('Deleting scores', () { group('DELETE', () {
test('deleteScore() ', () async { test('deleteScore() works correctly', () async {
await database.scoreEntryDao.addScore( await database.scoreEntryDao.addScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
@@ -377,7 +476,7 @@ void main() {
roundNumber: 1, roundNumber: 1,
); );
expect(deleted, true); expect(deleted, isTrue);
final score = await database.scoreEntryDao.getScore( final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
@@ -395,31 +494,36 @@ void main() {
roundNumber: 1, roundNumber: 1,
); );
expect(deleted, false); expect(deleted, isFalse);
}); });
test('deleteAllScoresForMatch() works correctly', () async { test('deleteAllScoresForMatch() works correctly', () async {
final score1 = ScoreEntry(roundNumber: 1, score: 10, change: 10);
await database.scoreEntryDao.addScore( await database.scoreEntryDao.addScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), entry: score1,
); );
final score2 = ScoreEntry(roundNumber: 1, score: 20, change: 20);
await database.scoreEntryDao.addScore( await database.scoreEntryDao.addScore(
playerId: testPlayer2.id, playerId: testPlayer2.id,
matchId: testMatch1.id, matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 20, change: 20), entry: score2,
); );
final score3 = ScoreEntry(roundNumber: 1, score: 15, change: 15);
await database.scoreEntryDao.addScore( await database.scoreEntryDao.addScore(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch2.id, matchId: testMatch2.id,
entry: ScoreEntry(roundNumber: 1, score: 15, change: 15), entry: score3,
); );
final deleted = await database.scoreEntryDao.deleteAllScoresForMatch( final deleted = await database.scoreEntryDao.deleteAllScoresForMatch(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(deleted, true); expect(deleted, isTrue);
final match1Scores = await database.scoreEntryDao.getAllMatchScores( final match1Scores = await database.scoreEntryDao.getAllMatchScores(
matchId: testMatch1.id, matchId: testMatch1.id,
@@ -433,22 +537,16 @@ void main() {
}); });
test('deleteAllScoresForPlayerInMatch() works correctly', () async { test('deleteAllScoresForPlayerInMatch() works correctly', () async {
await database.scoreEntryDao.addScore( await database.scoreEntryDao.addScoresAsList(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), entrys: [entryRound1, entryRound2],
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 2, score: 15, change: 5),
); );
await database.scoreEntryDao.addScore( await database.scoreEntryDao.addScore(
playerId: testPlayer2.id, playerId: testPlayer2.id,
matchId: testMatch1.id, matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 6, change: 6), entry: entryRound1,
); );
final deleted = await database.scoreEntryDao final deleted = await database.scoreEntryDao
@@ -457,7 +555,7 @@ void main() {
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(deleted, true); expect(deleted, isTrue);
final player1Scores = await database.scoreEntryDao final player1Scores = await database.scoreEntryDao
.getAllPlayerScoresInMatch( .getAllPlayerScoresInMatch(
@@ -475,146 +573,12 @@ void main() {
}); });
}); });
group('Score Aggregations & Edge Cases', () { group('WINNER', () {
test('getLatestRoundNumber()', () async {
var latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, isNull);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, 1);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 5, score: 50, change: 40),
);
latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, 5);
});
test('getLatestRoundNumber() with non-consecutive rounds', () async {
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 5, score: 50, change: 40),
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 3, score: 30, change: 20),
);
final latestRound = await database.scoreEntryDao.getLatestRoundNumber(
matchId: testMatch1.id,
);
expect(latestRound, 5);
});
test('getTotalScoreForPlayer()', () async {
var totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(totalScore, 0);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 2, score: 25, change: 15),
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 3, score: 40, change: 15),
);
totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
expect(totalScore, 40);
});
test('getTotalScoreForPlayer() ignores round score', () async {
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 2, score: 25, change: 25),
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 25, change: 10),
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 3, score: 25, change: 25),
);
final totalScore = await database.scoreEntryDao.getTotalScoreForPlayer(
playerId: testPlayer1.id,
matchId: testMatch1.id,
);
// Should return the sum of all changes
expect(totalScore, 60);
});
test('Adding the same score twice replaces the existing one', () async {
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 10, change: 10),
);
await database.scoreEntryDao.addScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
entry: ScoreEntry(roundNumber: 1, score: 20, change: 20),
);
final score = await database.scoreEntryDao.getScore(
playerId: testPlayer1.id,
matchId: testMatch1.id,
roundNumber: 1,
);
expect(score, isNotNull);
expect(score!.score, 20);
expect(score.change, 20);
});
});
group('Handling Winner', () {
test('hasWinner() works correctly', () async { test('hasWinner() works correctly', () async {
var hasWinner = await database.scoreEntryDao.hasWinner( var hasWinner = await database.scoreEntryDao.hasWinner(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(hasWinner, false); expect(hasWinner, isFalse);
await database.scoreEntryDao.setWinner( await database.scoreEntryDao.setWinner(
playerId: testPlayer1.id, playerId: testPlayer1.id,
@@ -624,7 +588,7 @@ void main() {
hasWinner = await database.scoreEntryDao.hasWinner( hasWinner = await database.scoreEntryDao.hasWinner(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(hasWinner, true); expect(hasWinner, isTrue);
}); });
test('getWinnersForMatch() returns correct winner', () async { test('getWinnersForMatch() returns correct winner', () async {
@@ -648,7 +612,7 @@ void main() {
var removed = await database.scoreEntryDao.removeWinner( var removed = await database.scoreEntryDao.removeWinner(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(removed, false); expect(removed, isFalse);
await database.scoreEntryDao.setWinner( await database.scoreEntryDao.setWinner(
playerId: testPlayer1.id, playerId: testPlayer1.id,
@@ -658,7 +622,7 @@ void main() {
removed = await database.scoreEntryDao.removeWinner( removed = await database.scoreEntryDao.removeWinner(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(removed, true); expect(removed, isTrue);
var winner = await database.scoreEntryDao.getWinner( var winner = await database.scoreEntryDao.getWinner(
matchId: testMatch1.id, matchId: testMatch1.id,
@@ -667,58 +631,58 @@ void main() {
}); });
}); });
group('Handling Looser', () { group('LOSER', () {
test('hasLooser() works correctly', () async { test('hasLoser() works correctly', () async {
var hasLooser = await database.scoreEntryDao.hasLooser( var hasLooser = await database.scoreEntryDao.hasLoser(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(hasLooser, false); expect(hasLooser, isFalse);
await database.scoreEntryDao.setLooser( await database.scoreEntryDao.setLoser(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
); );
hasLooser = await database.scoreEntryDao.hasLooser( hasLooser = await database.scoreEntryDao.hasLoser(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(hasLooser, true); expect(hasLooser, isTrue);
}); });
test('getLooser() returns correct winner', () async { test('getLoser() returns correct winner', () async {
var looser = await database.scoreEntryDao.getLooser( var looser = await database.scoreEntryDao.getLoser(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(looser, isNull); expect(looser, isNull);
await database.scoreEntryDao.setLooser( await database.scoreEntryDao.setLoser(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
); );
looser = await database.scoreEntryDao.getLooser(matchId: testMatch1.id); looser = await database.scoreEntryDao.getLoser(matchId: testMatch1.id);
expect(looser, isNotNull); expect(looser, isNotNull);
expect(looser!.id, testPlayer1.id); expect(looser!.id, testPlayer1.id);
}); });
test('removeLooser() works correctly', () async { test('removeLoser() works correctly', () async {
var removed = await database.scoreEntryDao.removeLooser( var removed = await database.scoreEntryDao.removeLoser(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(removed, false); expect(removed, isFalse);
await database.scoreEntryDao.setLooser( await database.scoreEntryDao.setLoser(
playerId: testPlayer1.id, playerId: testPlayer1.id,
matchId: testMatch1.id, matchId: testMatch1.id,
); );
removed = await database.scoreEntryDao.removeLooser( removed = await database.scoreEntryDao.removeLoser(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(removed, true); expect(removed, isTrue);
var looser = await database.scoreEntryDao.getLooser( var looser = await database.scoreEntryDao.getLoser(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(looser, isNull); expect(looser, isNull);

View File

@@ -99,8 +99,8 @@ void main() {
await database.playerDao.addPlayer(player: testPlayer1); await database.playerDao.addPlayer(player: testPlayer1);
await database.gameDao.addGame(game: testGame); await database.gameDao.addGame(game: testGame);
await database.groupDao.addGroup(group: testGroup); await database.groupDao.addGroup(group: testGroup);
await database.teamDao.addTeam(team: testTeam);
await database.matchDao.addMatch(match: testMatch); await database.matchDao.addMatch(match: testMatch);
await database.teamDao.addTeam(team: testTeam, matchId: testMatch.id);
var playerCount = await database.playerDao.getPlayerCount(); var playerCount = await database.playerDao.getPlayerCount();
var gameCount = await database.gameDao.getGameCount(); var gameCount = await database.gameDao.getGameCount();
@@ -137,7 +137,9 @@ void main() {
await database.playerDao.addPlayer(player: testPlayer2); await database.playerDao.addPlayer(player: testPlayer2);
await database.gameDao.addGame(game: testGame); await database.gameDao.addGame(game: testGame);
await database.groupDao.addGroup(group: testGroup); await database.groupDao.addGroup(group: testGroup);
/*
await database.teamDao.addTeam(team: testTeam); await database.teamDao.addTeam(team: testTeam);
*/
await database.matchDao.addMatch(match: testMatch); await database.matchDao.addMatch(match: testMatch);
final ctx = await getContext(tester); final ctx = await getContext(tester);
@@ -147,22 +149,19 @@ void main() {
final decoded = json.decode(jsonString) as Map<String, dynamic>; final decoded = json.decode(jsonString) as Map<String, dynamic>;
expect(decoded.containsKey('players'), true); expect(decoded.containsKey('players'), isTrue);
expect(decoded.containsKey('games'), true); expect(decoded.containsKey('games'), isTrue);
expect(decoded.containsKey('groups'), true); expect(decoded.containsKey('groups'), isTrue);
expect(decoded.containsKey('teams'), true); expect(decoded.containsKey('matches'), isTrue);
expect(decoded.containsKey('matches'), true);
final players = decoded['players'] as List<dynamic>; final players = decoded['players'] as List<dynamic>;
final games = decoded['games'] as List<dynamic>; final games = decoded['games'] as List<dynamic>;
final groups = decoded['groups'] as List<dynamic>; final groups = decoded['groups'] as List<dynamic>;
final teams = decoded['teams'] as List<dynamic>;
final matches = decoded['matches'] as List<dynamic>; final matches = decoded['matches'] as List<dynamic>;
expect(players.length, 2); expect(players.length, 2);
expect(games.length, 1); expect(games.length, 1);
expect(groups.length, 1); expect(groups.length, 1);
expect(teams.length, 1);
expect(matches.length, 1); expect(matches.length, 1);
}); });
@@ -175,13 +174,11 @@ void main() {
final players = decoded['players'] as List<dynamic>; final players = decoded['players'] as List<dynamic>;
final games = decoded['games'] as List<dynamic>; final games = decoded['games'] as List<dynamic>;
final groups = decoded['groups'] as List<dynamic>; final groups = decoded['groups'] as List<dynamic>;
final teams = decoded['teams'] as List<dynamic>;
final matches = decoded['matches'] as List<dynamic>; final matches = decoded['matches'] as List<dynamic>;
expect(players, isEmpty); expect(players, isEmpty);
expect(games, isEmpty); expect(games, isEmpty);
expect(groups, isEmpty); expect(groups, isEmpty);
expect(teams, isEmpty);
expect(matches, isEmpty); expect(matches, isEmpty);
}); });
}); });
@@ -243,29 +240,6 @@ void main() {
expect(memberIds, containsAll([testPlayer1.id, testPlayer2.id])); expect(memberIds, containsAll([testPlayer1.id, testPlayer2.id]));
}); });
testWidgets('Team data is correct', (tester) async {
await database.teamDao.addTeam(team: testTeam);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final teams = decoded['teams'] as List<dynamic>;
expect(teams.length, 1);
final teamData = teams[0] as Map<String, dynamic>;
expect(teamData['id'], testTeam.id);
expect(teamData['name'], testTeam.name);
expect(teamData['memberIds'], isA<List>());
// Note: In this system, teams don't have independent members.
// Team members are only tracked through matches via PlayerMatchTable.
// Therefore, memberIds will be empty for standalone teams.
final memberIds = teamData['memberIds'] as List<dynamic>;
expect(memberIds, isEmpty);
});
testWidgets('Match data is correct', (tester) async { testWidgets('Match data is correct', (tester) async {
await database.playerDao.addPlayersAsList( await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer2], players: [testPlayer1, testPlayer2],
@@ -317,6 +291,51 @@ void main() {
expect(player2Score.change, 15); expect(player2Score.change, 15);
}); });
testWidgets('Match with teams is handled correctly', (tester) async {
final matchWithTeams = Match(
name: 'Match with Teams',
game: testGame,
players: [testPlayer1, testPlayer2],
teams: [testTeam],
notes: 'Team match',
);
await database.playerDao.addPlayersAsList(
players: [testPlayer1, testPlayer2],
);
await database.gameDao.addGame(game: testGame);
await database.matchDao.addMatch(match: matchWithTeams);
final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx);
final decoded = json.decode(jsonString) as Map<String, dynamic>;
final matches = decoded['matches'] as List<dynamic>;
expect(matches.length, 1);
final matchData = matches[0] as Map<String, dynamic>;
expect(matchData['id'], matchWithTeams.id);
expect(matchData['name'], matchWithTeams.name);
expect(
matchData['teams'],
isNotNull,
reason: 'teams should not be null',
);
expect(matchData['teams'], isA<List>());
final teamsInMatch = matchData['teams'] as List<dynamic>;
expect(teamsInMatch.length, 1);
final teamData = teamsInMatch[0] as Map<String, dynamic>;
expect(teamData['id'], testTeam.id);
expect(teamData['name'], testTeam.name);
expect(teamData['memberIds'], isA<List>());
final memberIds = teamData['memberIds'] as List<dynamic>;
expect(memberIds.length, 2);
expect(memberIds, containsAll([testPlayer1.id, testPlayer2.id]));
});
testWidgets('Match without group is handled correctly', (tester) async { testWidgets('Match without group is handled correctly', (tester) async {
final matchWithoutGroup = Match( final matchWithoutGroup = Match(
name: 'No Group Match', name: 'No Group Match',
@@ -644,19 +663,17 @@ void main() {
test('parseTeamsFromJson()', () { test('parseTeamsFromJson()', () {
final playerById = {testPlayer1.id: testPlayer1}; final playerById = {testPlayer1.id: testPlayer1};
final jsonMap = { final teamsJson = [
'teams': [ {
{ 'id': testTeam.id,
'id': testTeam.id, 'name': testTeam.name,
'name': testTeam.name, 'memberIds': [testPlayer1.id],
'memberIds': [testPlayer1.id], 'createdAt': testTeam.createdAt.toIso8601String(),
'createdAt': testTeam.createdAt.toIso8601String(), },
}, ];
],
};
final teams = DataTransferService.parseTeamsFromJson( final teams = DataTransferService.parseTeamsFromJson(
jsonMap, teamsJson,
playerById, playerById,
); );
@@ -668,15 +685,21 @@ void main() {
}); });
test('parseTeamsFromJson() empty list', () { test('parseTeamsFromJson() empty list', () {
final jsonMap = {'teams': []}; final teams = DataTransferService.parseTeamsFromJson([], {});
final teams = DataTransferService.parseTeamsFromJson(jsonMap, {});
expect(teams, isEmpty); expect(teams, isEmpty);
}); });
test('parseTeamsFromJson() missing key', () { test('parseTeamsFromJson() missing memberIds', () {
final jsonMap = <String, dynamic>{}; final teamsJson = [
final teams = DataTransferService.parseTeamsFromJson(jsonMap, {}); {
expect(teams, isEmpty); 'id': testTeam.id,
'name': testTeam.name,
'createdAt': testTeam.createdAt.toIso8601String(),
},
];
final teams = DataTransferService.parseTeamsFromJson(teamsJson, {});
expect(teams.length, 1);
expect(teams[0].members, isEmpty);
}); });
test('parseMatchesFromJson()', () { test('parseMatchesFromJson()', () {