From f49440367cb3b5d33a780ac3c4c9ebf8c92104ec Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 1 May 2026 14:31:15 +0200 Subject: [PATCH] Finalized and updated team implementation, updated team dao, tests, and ex-/import --- lib/data/dao/match_dao.dart | 50 +- lib/data/dao/team_dao.dart | 184 +++-- lib/data/dao/team_dao.g.dart | 19 + lib/data/db/database.g.dart | 9 +- lib/data/db/tables/player_match_table.dart | 4 +- lib/data/models/match.dart | 5 + lib/services/data_transfer_service.dart | 14 +- test/db_tests/aggregates/team_test.dart | 704 +++++++----------- test/services/data_transfer_service_test.dart | 44 +- 9 files changed, 480 insertions(+), 553 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 3df8b9d..faa0227 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -8,6 +8,7 @@ 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/player.dart'; +import 'package:tallee/data/models/team.dart'; part 'match_dao.g.dart'; @@ -17,7 +18,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /* Create */ - /// Adds a new [Match] to the database. Also adds players associations. + /// Adds a new [Match] to the database. Also adds players associations and teams. /// This method assumes that the game and group (if any) are already present /// in the database. Future addMatch({required Match match}) async { @@ -36,6 +37,11 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { mode: InsertMode.insertOrReplace, ); + // Add teams + if (match.teams != null && match.teams!.isNotEmpty) { + await db.teamDao.addTeamsAsList(teams: match.teams!, matchId: match.id); + } + for (final p in match.players) { await db.playerMatchDao.addPlayerToMatch( matchId: match.id, @@ -53,7 +59,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } } - print('Return true'); }); return true; } @@ -220,6 +225,16 @@ class MatchDao extends DatabaseAccessor 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; } @@ -262,12 +277,15 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { 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, @@ -294,12 +312,15 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { 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, @@ -319,12 +340,14 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { final group = await db.groupDao.getGroupById(groupId: groupId); final players = await db.playerMatchDao.getPlayersOfMatch(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, @@ -333,6 +356,29 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } + /// Helper method to retrieve teams for a specific match + Future> _getMatchTeams({required String matchId}) async { + // Get all unique team IDs from PlayerMatchTable for this match + final playerMatchQuery = select(db.playerMatchTable) + ..where((pm) => pm.matchId.equals(matchId) & pm.teamId.isNotNull()); + final playerMatches = await playerMatchQuery.get(); + + if (playerMatches.isEmpty) return []; + + final teamIds = playerMatches + .map((pm) => pm.teamId) + .whereType() + .toSet() + .toList(); + + // Fetch all teams + final teams = await Future.wait( + teamIds.map((teamId) => db.teamDao.getTeamById(teamId: teamId)), + ); + + return teams; + } + /* Update */ /// Changes the name of the match with the given [matchId] to [newName]. diff --git a/lib/data/dao/team_dao.dart b/lib/data/dao/team_dao.dart index 01dc724..708b475 100644 --- a/lib/data/dao/team_dao.dart +++ b/lib/data/dao/team_dao.dart @@ -1,17 +1,105 @@ import 'package:drift/drift.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/models/player.dart'; import 'package:tallee/data/models/team.dart'; part 'team_dao.g.dart'; -@DriftAccessor(tables: [TeamTable]) +@DriftAccessor(tables: [TeamTable, PlayerMatchTable]) class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { TeamDao(super.db); + /* Create */ + + /// Adds a new [team] to the database. + /// Returns `true` if the team was added, `false` otherwise. + Future 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 addTeamsAsList({ + required List 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 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 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. - /// Note: This returns teams without their members. Use getTeamById for full team data. Future> getAllTeams() async { final query = select(teamTable); final result = await query.get(); @@ -41,8 +129,7 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { ); } - /// Helper method to get team members from player_match_table. - /// This assumes team members are tracked via the player_match_table. + /// Helper method to get team members from PlayerMatchTable. Future> _getTeamMembers({required String teamId}) async { // Get all player_match entries with this teamId final playerMatchQuery = select(db.playerMatchTable) @@ -61,44 +148,28 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { return players; } - /// Adds a new [team] to the database. - /// Returns `true` if the team was added, `false` otherwise. - Future addTeam({required Team team}) async { - if (!await teamExists(teamId: team.id)) { - await into(teamTable).insert( - TeamTableCompanion.insert( - id: team.id, - name: team.name, - createdAt: team.createdAt, - ), - mode: InsertMode.insertOrReplace, - ); - return true; - } - return false; + /* Update */ + + /// Updates the name of the team with the given [teamId]. + Future updateTeamName({ + required String teamId, + required String newName, + }) async { + final rowsAffected = + await (update(teamTable)..where((t) => t.id.equals(teamId))).write( + TeamTableCompanion(name: Value(newName)), + ); + return rowsAffected > 0; } - /// Adds multiple [teams] to the database in a batch operation. - Future addTeamsAsList({required List teams}) async { - if (teams.isEmpty) return false; + /* Delete */ - 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, - ), - ); - - return true; + /// Deletes all teams from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllTeams() async { + final query = delete(teamTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; } /// Deletes the team with the given [teamId] from the database. @@ -108,39 +179,4 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { final rowsAffected = await query.go(); return rowsAffected > 0; } - - /// Checks if a team with the given [teamId] exists in the database. - /// Returns `true` if the team exists, `false` otherwise. - Future 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 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 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 deleteAllTeams() async { - final query = delete(teamTable); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } } diff --git a/lib/data/dao/team_dao.g.dart b/lib/data/dao/team_dao.g.dart index 3b78c03..7b468dd 100644 --- a/lib/data/dao/team_dao.g.dart +++ b/lib/data/dao/team_dao.g.dart @@ -5,6 +5,12 @@ part of 'team_dao.dart'; // ignore_for_file: type=lint mixin _$TeamDaoMixin on DatabaseAccessor { $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); } @@ -13,4 +19,17 @@ class TeamDaoManager { TeamDaoManager(this._db); $$TeamTableTableTableManager get 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, + ); } diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 2190c3d..b4f3702 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -2122,7 +2122,7 @@ class $PlayerMatchTableTable extends PlayerMatchTable type: DriftSqlType.string, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES team_table (id)', + 'REFERENCES team_table (id) ON DELETE SET NULL', ), ); @override @@ -2820,6 +2820,13 @@ abstract class _$AppDatabase extends GeneratedDatabase { ), 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( on: TableUpdateQuery.onTableName( 'player_table', diff --git a/lib/data/db/tables/player_match_table.dart b/lib/data/db/tables/player_match_table.dart index 30412ab..50dda0f 100644 --- a/lib/data/db/tables/player_match_table.dart +++ b/lib/data/db/tables/player_match_table.dart @@ -8,7 +8,9 @@ class PlayerMatchTable extends Table { text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); TextColumn get matchId => 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 Set> get primaryKey => {playerId, matchId}; diff --git a/lib/data/models/match.dart b/lib/data/models/match.dart index 2ff02d6..b0b487c 100644 --- a/lib/data/models/match.dart +++ b/lib/data/models/match.dart @@ -4,6 +4,7 @@ import 'package:tallee/data/models/game.dart'; import 'package:tallee/data/models/group.dart'; import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/score_entry.dart'; +import 'package:tallee/data/models/team.dart'; import 'package:uuid/uuid.dart'; class Match { @@ -14,6 +15,7 @@ class Match { final Game game; final Group? group; final List players; + final List? teams; final String notes; Map scores; @@ -23,6 +25,7 @@ class Match { required this.players, this.endedAt, this.group, + this.teams, this.notes = '', String? id, DateTime? createdAt, @@ -55,6 +58,7 @@ class Match { ), group = null, players = [], + teams = [], scores = json['scores'] != null ? (json['scores'] as Map).map( (key, value) => MapEntry( @@ -78,6 +82,7 @@ class Match { 'gameId': game.id, 'groupId': group?.id, 'playerIds': players.map((player) => player.id).toList(), + 'teams': teams?.map((team) => team.toJson()).toList(), 'scores': scores.map((key, value) => MapEntry(key, value?.toJson())), 'notes': notes, }; diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index e47b220..29199f8 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -35,13 +35,11 @@ class DataTransferService { final groups = await db.groupDao.getAllGroups(); final players = await db.playerDao.getAllPlayers(); final games = await db.gameDao.getAllGames(); - final teams = await db.teamDao.getAllTeams(); final Map jsonMap = { 'players': players.map((player) => player.toJson()).toList(), 'games': games.map((game) => game.toJson()).toList(), 'groups': groups.map((group) => group.toJson()).toList(), - 'teams': teams.map((team) => team.toJson()).toList(), 'matches': matches.map((match) => match.toJson()).toList(), }; @@ -130,8 +128,6 @@ class DataTransferService { final importedGroups = parseGroupsFromJson(decodedJson, playerById); final groupById = {for (final g in importedGroups) g.id: g}; - final importedTeams = parseTeamsFromJson(decodedJson, playerById); - final importedMatches = parseMatchesFromJson( decodedJson, gameById, @@ -142,7 +138,6 @@ class DataTransferService { await db.playerDao.addPlayersAsList(players: importedPlayers); await db.gameDao.addGamesAsList(games: importedGames); await db.groupDao.addGroupsAsList(groups: importedGroups); - await db.teamDao.addTeamsAsList(teams: importedTeams); await db.matchDao.addMatchesAsList(matches: importedMatches); } @@ -190,13 +185,12 @@ class DataTransferService { }).toList(); } - /// Parses teams from JSON data. + /// Parses teams from a list of JSON objects. @visibleForTesting static List parseTeamsFromJson( - Map decodedJson, + List teamsJson, Map playerById, ) { - final teamsJson = (decodedJson['teams'] as List?) ?? []; return teamsJson.map((t) { final map = t as Map; final memberIds = (map['memberIds'] as List? ?? []) @@ -259,12 +253,16 @@ class DataTransferService { .whereType() .toList(); + final teamsJson = (map['teams'] as List?) ?? []; + final teams = parseTeamsFromJson(teamsJson, playersMap); + return Match( id: id, name: name, game: game, group: group, players: players, + teams: teams.isEmpty ? null : teams, createdAt: createdAt, endedAt: endedAt, notes: notes, diff --git a/test/db_tests/aggregates/team_test.dart b/test/db_tests/aggregates/team_test.dart index 39c5be5..592810d 100644 --- a/test/db_tests/aggregates/team_test.dart +++ b/test/db_tests/aggregates/team_test.dart @@ -1,3 +1,5 @@ +import 'dart:core' hide Match; + import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; @@ -18,8 +20,11 @@ void main() { late Team testTeam1; late Team testTeam2; late Team testTeam3; - late Game testGame1; - late Game testGame2; + late Team testTeam4; + late Game testGame; + late Match testMatch1; + late Match testMatch2; + late Match matchWithNoTeams; final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -40,27 +45,35 @@ void main() { testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]); testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]); testTeam3 = Team(name: 'Team Gamma', members: [testPlayer1, testPlayer3]); - testGame1 = Game( - name: 'Game 1', - ruleset: Ruleset.singleWinner, - description: 'Test game 1', + testTeam4 = Team(name: 'Team Omega', members: [testPlayer2, testPlayer4]); + testGame = Game( + name: 'Test Game', + ruleset: Ruleset.highestScore, color: GameColor.blue, icon: '', ); - testGame2 = Game( - name: 'Game 2', - ruleset: Ruleset.highestScore, - description: 'Test game 2', - color: GameColor.red, - icon: '', + testMatch1 = Match( + name: 'Match 1', + game: testGame, + players: [], + teams: [testTeam1, testTeam2], + ); + 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( players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGame2); }); tearDown(() async { @@ -68,460 +81,251 @@ void main() { }); group('Team Tests', () { - // Verifies that a single team can be added and retrieved with all fields intact. - test('Adding and fetching a single team works correctly', () async { - final added = await database.teamDao.addTeam(team: testTeam1); - expect(added, true); - - final fetchedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - - expect(fetchedTeam.id, testTeam1.id); - expect(fetchedTeam.name, testTeam1.name); - expect(fetchedTeam.createdAt, testTeam1.createdAt); - }); - - // Verifies that multiple teams can be added at once and retrieved correctly. - test('Adding and fetching multiple teams works correctly', () async { - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam2, testTeam3], - ); - - final allTeams = await database.teamDao.getAllTeams(); - expect(allTeams.length, 3); - - final testTeams = { - testTeam1.id: testTeam1, - testTeam2.id: testTeam2, - testTeam3.id: testTeam3, - }; - - for (final team in allTeams) { - final testTeam = testTeams[team.id]!; - - expect(team.id, testTeam.id); - expect(team.name, testTeam.name); - expect(team.createdAt, testTeam.createdAt); - } - }); - - // 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 { - await database.teamDao.addTeam(team: testTeam1); - final addedAgain = await database.teamDao.addTeam(team: testTeam1); - - expect(addedAgain, false); - - final teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 1); - }); - - // Verifies that teamExists returns correct boolean based on team presence. - test('Team existence check works correctly', () async { - var teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); - expect(teamExists, false); - - await database.teamDao.addTeam(team: testTeam1); - - teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); - expect(teamExists, true); - }); - - // Verifies that deleteTeam removes the team and returns true. - test('Deleting a team works correctly', () async { - await database.teamDao.addTeam(team: testTeam1); - - final teamDeleted = await database.teamDao.deleteTeam( - teamId: testTeam1.id, - ); - expect(teamDeleted, true); - - final teamExists = await database.teamDao.teamExists( - teamId: testTeam1.id, - ); - expect(teamExists, false); - }); - - // Verifies that deleteTeam returns false for a non-existent team ID. - test('Deleting a non-existent team returns false', () async { - final teamDeleted = await database.teamDao.deleteTeam( - teamId: 'non-existent-id', - ); - expect(teamDeleted, false); - }); - - // Verifies that getTeamCount returns correct count through add/delete operations. - 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()), - ); - }); - - // 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, + group('CREATE', () { + test('Adding and fetching a single team works correctly', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); + final added = await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); + expect(added, isTrue); + + final fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + + expect(fetchedTeam.id, testTeam1.id); + expect(fetchedTeam.name, testTeam1.name); + expect(fetchedTeam.createdAt, testTeam1.createdAt); + expect(fetchedTeam.members.length, testTeam1.members.length); + 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); + } + }); + + test('Adding and fetching multiple teams 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); + + final testTeams = { + testTeam1.id: testTeam1, + testTeam2.id: testTeam2, + testTeam3.id: testTeam3, + }; + + for (final team in allTeams) { + final testTeam = testTeams[team.id]!; + + expect(team.id, testTeam.id); + expect(team.name, testTeam.name); + expect(team.createdAt, testTeam.createdAt); + } + }); + + test('addTeam() ignores duplicates', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); + var added = await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); + expect(added, isTrue); + + added = await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); + expect(added, isFalse); + + final teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 1); + }); + + test('addTeamsAsList() with empty list returns isFalse', () async { + final added = await database.teamDao.addTeamsAsList( + teams: [], + matchId: matchWithNoTeams.id, + ); + expect(added, isFalse); + }); + + test('addTeamsAsList() ignores duplicates', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); + final added = await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam1], + matchId: matchWithNoTeams.id, + ); + expect(added, isTrue); + + final teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 2); }); }); - // Verifies that teamExists returns false for deleted teams. - test('Team existence returns false after deletion', () async { - await database.teamDao.addTeam(team: testTeam1); - expect(await database.teamDao.teamExists(teamId: testTeam1.id), true); + group('READ', () { + test('getTeamCount works correctly', () async { + var count = await database.teamDao.getTeamCount(); + expect(count, 0); - await database.teamDao.deleteTeam(teamId: testTeam1.id); - expect(await database.teamDao.teamExists(teamId: testTeam1.id), false); + await database.matchDao.addMatch(match: testMatch1); + + 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>()); + 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()), + ); + }); }); - // Verifies that adding multiple teams in batch then deleting returns correct count. - test('Batch add then partial delete maintains correct count', () async { - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam2, testTeam3], - ); + group('UPDATED', () { + test('updateTeamName() works correctly', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); - expect(await database.teamDao.getTeamCount(), 3); + await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); - await database.teamDao.deleteTeam(teamId: testTeam1.id); - expect(await database.teamDao.getTeamCount(), 2); + var fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + expect(fetchedTeam.name, testTeam1.name); - await database.teamDao.deleteTeam(teamId: testTeam3.id); - expect(await database.teamDao.getTeamCount(), 1); + const newName = 'New name'; + await database.teamDao.updateTeamName( + teamId: testTeam1.id, + newName: 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', + newName: 'New Name', + ); + expect(updated, isFalse); + + final allTeams = await database.teamDao.getAllTeams(); + expect(allTeams, isEmpty); + }); }); - // Verifies that deleteAllTeams with single team works. - test('Deleting all teams with single team returns true', () async { - await database.teamDao.addTeam(team: testTeam1); - expect(await database.teamDao.getTeamCount(), 1); + group('DELETE', () { + test('deleteTeam() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.matchDao.addMatch(match: matchWithNoTeams); - final deleted = await database.teamDao.deleteAllTeams(); - expect(deleted, true); - expect(await database.teamDao.getTeamCount(), 0); - }); + await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); - // Verifies that addTeam after deleteAllTeams works correctly. - test('Adding team after deleteAllTeams works correctly', () async { - await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam2]); - expect(await database.teamDao.getTeamCount(), 2); + final deleted = await database.teamDao.deleteTeam(teamId: testTeam1.id); + expect(deleted, isTrue); - await database.teamDao.deleteAllTeams(); - expect(await database.teamDao.getTeamCount(), 0); + final teamExists = await database.teamDao.teamExists( + teamId: testTeam1.id, + ); + expect(teamExists, isFalse); + }); - final added = await database.teamDao.addTeam(team: testTeam3); - expect(added, true); - expect(await database.teamDao.getTeamCount(), 1); + test('Deleting a non-existent team returns isFalse', () async { + final deleted = await database.teamDao.deleteTeam( + teamId: 'non-existent-id', + ); + expect(deleted, isFalse); + }); - final fetchedTeam = await database.teamDao.getTeamById( - teamId: testTeam3.id, - ); - expect(fetchedTeam.name, testTeam3.name); - }); + test('deleteAllTeams() works correctly', () async { + await database.matchDao.addMatchesAsList( + matches: [testMatch1, testMatch2], + ); + var teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 4); - // Verifies that addTeamsAsList with partial duplicates ignores duplicates. - test('Adding teams with some duplicates ignores only duplicates', () async { - await database.teamDao.addTeam(team: testTeam1); + final deleted = await database.teamDao.deleteAllTeams(); + expect(deleted, isTrue); - final duplicateTeam1 = Team( - id: testTeam1.id, - name: 'Different Name', - members: [testPlayer3], - ); + teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 0); + }); - await database.teamDao.addTeamsAsList( - teams: [duplicateTeam1, testTeam2, testTeam3], - ); - - 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); + test('deleteAllTeams() with empty list returns false', () async { + final deleted = await database.teamDao.deleteAllTeams(); + expect(deleted, isFalse); + }); }); }); } diff --git a/test/services/data_transfer_service_test.dart b/test/services/data_transfer_service_test.dart index e863629..6aec390 100644 --- a/test/services/data_transfer_service_test.dart +++ b/test/services/data_transfer_service_test.dart @@ -99,7 +99,9 @@ void main() { await database.playerDao.addPlayer(player: testPlayer1); await database.gameDao.addGame(game: testGame); await database.groupDao.addGroup(group: testGroup); + /* await database.teamDao.addTeam(team: testTeam); +*/ await database.matchDao.addMatch(match: testMatch); var playerCount = await database.playerDao.getPlayerCount(); @@ -137,7 +139,9 @@ void main() { await database.playerDao.addPlayer(player: testPlayer2); await database.gameDao.addGame(game: testGame); await database.groupDao.addGroup(group: testGroup); + /* await database.teamDao.addTeam(team: testTeam); +*/ await database.matchDao.addMatch(match: testMatch); final ctx = await getContext(tester); @@ -244,7 +248,9 @@ void main() { }); testWidgets('Team data is correct', (tester) async { + /* await database.teamDao.addTeam(team: testTeam); +*/ final ctx = await getContext(tester); final jsonString = await DataTransferService.getAppDataAsJson(ctx); @@ -644,19 +650,17 @@ void main() { test('parseTeamsFromJson()', () { final playerById = {testPlayer1.id: testPlayer1}; - final jsonMap = { - 'teams': [ - { - 'id': testTeam.id, - 'name': testTeam.name, - 'memberIds': [testPlayer1.id], - 'createdAt': testTeam.createdAt.toIso8601String(), - }, - ], - }; + final teamsJson = [ + { + 'id': testTeam.id, + 'name': testTeam.name, + 'memberIds': [testPlayer1.id], + 'createdAt': testTeam.createdAt.toIso8601String(), + }, + ]; final teams = DataTransferService.parseTeamsFromJson( - jsonMap, + teamsJson, playerById, ); @@ -668,15 +672,21 @@ void main() { }); test('parseTeamsFromJson() empty list', () { - final jsonMap = {'teams': []}; - final teams = DataTransferService.parseTeamsFromJson(jsonMap, {}); + final teams = DataTransferService.parseTeamsFromJson([], {}); expect(teams, isEmpty); }); - test('parseTeamsFromJson() missing key', () { - final jsonMap = {}; - final teams = DataTransferService.parseTeamsFromJson(jsonMap, {}); - expect(teams, isEmpty); + test('parseTeamsFromJson() missing memberIds', () { + final teamsJson = [ + { + '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()', () {