Finalized and updated team implementation, updated team dao, tests, and ex-/import

This commit is contained in:
2026-05-01 14:31:15 +02:00
parent be6b968a43
commit f49440367c
9 changed files with 480 additions and 553 deletions

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';
@@ -17,7 +18,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
/* Create */ /* 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 /// This method assumes that the game and group (if any) are already present
/// in the database. /// in the database.
Future<bool> addMatch({required Match match}) async { Future<bool> addMatch({required Match match}) async {
@@ -36,6 +37,11 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
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);
}
for (final p in match.players) { for (final p in match.players) {
await db.playerMatchDao.addPlayerToMatch( await db.playerMatchDao.addPlayerToMatch(
matchId: match.id, matchId: match.id,
@@ -53,7 +59,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
); );
} }
} }
print('Return true');
}); });
return true; return true;
} }
@@ -220,6 +225,16 @@ 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; return true;
} }
@@ -262,12 +277,15 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
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,
teams: teams.isEmpty ? null : teams,
notes: row.notes ?? '', notes: row.notes ?? '',
createdAt: row.createdAt, createdAt: row.createdAt,
endedAt: row.endedAt, endedAt: row.endedAt,
@@ -294,12 +312,15 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId); final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId);
final teams = await _getMatchTeams(matchId: matchId);
return Match( return Match(
id: result.id, id: result.id,
name: result.name, name: result.name,
game: game, game: game,
group: group, group: group,
players: players, players: players,
teams: teams.isEmpty ? null : teams,
notes: result.notes ?? '', notes: result.notes ?? '',
createdAt: result.createdAt, createdAt: result.createdAt,
endedAt: result.endedAt, endedAt: result.endedAt,
@@ -319,12 +340,14 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
final group = await db.groupDao.getGroupById(groupId: groupId); final group = await db.groupDao.getGroupById(groupId: groupId);
final players = final players =
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; await db.playerMatchDao.getPlayersOfMatch(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,
teams: teams.isEmpty ? null : teams,
notes: row.notes ?? '', notes: row.notes ?? '',
createdAt: row.createdAt, createdAt: row.createdAt,
endedAt: row.endedAt, endedAt: row.endedAt,
@@ -333,6 +356,29 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
); );
} }
/// Helper method to retrieve teams for a specific match
Future<List<Team>> _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<String>()
.toSet()
.toList();
// Fetch all teams
final teams = await Future.wait(
teamIds.map((teamId) => db.teamDao.getTeamById(teamId: teamId)),
);
return teams;
}
/* Update */ /* Update */
/// Changes the name of the match with the given [matchId] to [newName]. /// Changes the name of the match with the given [matchId] to [newName].

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 newName,
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(newName)),
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

@@ -2122,7 +2122,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 +2820,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',

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

@@ -4,6 +4,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/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 +15,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 +25,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,
@@ -55,6 +58,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(
@@ -78,6 +82,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

@@ -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,7 +138,6 @@ 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.addMatchesAsList(matches: importedMatches);
} }
@@ -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,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,
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. 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

@@ -99,7 +99,9 @@ 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.teamDao.addTeam(team: testTeam);
*/
await database.matchDao.addMatch(match: testMatch); await database.matchDao.addMatch(match: testMatch);
var playerCount = await database.playerDao.getPlayerCount(); var playerCount = await database.playerDao.getPlayerCount();
@@ -137,7 +139,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);
@@ -244,7 +248,9 @@ void main() {
}); });
testWidgets('Team data is correct', (tester) async { testWidgets('Team data is correct', (tester) async {
/*
await database.teamDao.addTeam(team: testTeam); await database.teamDao.addTeam(team: testTeam);
*/
final ctx = await getContext(tester); final ctx = await getContext(tester);
final jsonString = await DataTransferService.getAppDataAsJson(ctx); final jsonString = await DataTransferService.getAppDataAsJson(ctx);
@@ -644,19 +650,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 +672,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()', () {