diff --git a/assets/schema.json b/assets/schema.json index 8021012..f5e363b 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -20,6 +20,7 @@ "type": "string" } }, + "additionalProperties": false, "required": [ "id", "createdAt", @@ -55,6 +56,7 @@ "type": "string" } }, + "additionalProperties": false, "required": [ "id", "createdAt", @@ -90,6 +92,7 @@ } } }, + "additionalProperties": false, "required": [ "id", "name", @@ -120,6 +123,7 @@ } } }, + "additionalProperties": false, "required": [ "id", "name", @@ -157,10 +161,31 @@ "type": "string" } }, + "scores": { + "type": "object", + "items": { + "type": "array", + "items": { + "type": "string", + "properties": { + "roundNumber": { + "type": "number" + }, + "score": { + "type": "number" + }, + "change": { + "type": "number" + } + } + } + } + }, "notes": { "type": "string" } }, + "additionalProperties": false, "required": [ "id", "name", @@ -172,6 +197,7 @@ } } }, + "additionalProperties": false, "required": [ "players", "games", diff --git a/lib/core/common.dart b/lib/core/common.dart index a27daf0..20b0225 100644 --- a/lib/core/common.dart +++ b/lib/core/common.dart @@ -1,6 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:tallee/core/enums.dart'; -import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/models/match.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; /// Translates a [Ruleset] enum value to its corresponding localized string. diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 8e658ed..f07e2c7 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -1,8 +1,8 @@ import 'package:drift/drift.dart'; +import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/db/tables/game_table.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/models/game.dart'; part 'game_dao.g.dart'; @@ -111,14 +111,20 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { } /// Updates the name of the game with the given [gameId] to [newName]. - Future updateGameName({required String gameId, required String newName}) async { - await (update( - gameTable, - )..where((g) => g.id.equals(gameId))).write(GameTableCompanion(name: Value(newName))); + Future updateGameName({ + required String gameId, + required String newName, + }) async { + await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + GameTableCompanion(name: Value(newName)), + ); } /// Updates the ruleset of the game with the given [gameId]. - Future updateGameRuleset({required String gameId, required Ruleset newRuleset}) async { + Future updateGameRuleset({ + required String gameId, + required Ruleset newRuleset, + }) async { await (update(gameTable)..where((g) => g.id.equals(gameId))).write( GameTableCompanion(ruleset: Value(newRuleset.name)), ); @@ -135,24 +141,31 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { } /// Updates the color of the game with the given [gameId]. - Future updateGameColor({required String gameId, required GameColor newColor}) async { - await (update( - gameTable, - )..where((g) => g.id.equals(gameId))).write(GameTableCompanion(color: Value(newColor.name))); + Future updateGameColor({ + required String gameId, + required GameColor newColor, + }) async { + await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + GameTableCompanion(color: Value(newColor.name)), + ); } /// Updates the icon of the game with the given [gameId]. - Future updateGameIcon({required String gameId, required String newIcon}) async { - await (update( - gameTable, - )..where((g) => g.id.equals(gameId))).write(GameTableCompanion(icon: Value(newIcon))); + Future updateGameIcon({ + required String gameId, + required String newIcon, + }) async { + await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + GameTableCompanion(icon: Value(newIcon)), + ); } /// Retrieves the total count of games in the database. Future getGameCount() async { - final count = await (selectOnly( - gameTable, - )..addColumns([gameTable.id.count()])).map((row) => row.read(gameTable.id.count())).getSingle(); + final count = + await (selectOnly(gameTable)..addColumns([gameTable.id.count()])) + .map((row) => row.read(gameTable.id.count())) + .getSingle(); return count ?? 0; } diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart index b5a29fe..a998fe7 100644 --- a/lib/data/dao/game_dao.g.dart +++ b/lib/data/dao/game_dao.g.dart @@ -5,4 +5,12 @@ part of 'game_dao.dart'; // ignore_for_file: type=lint mixin _$GameDaoMixin on DatabaseAccessor { $GameTableTable get gameTable => attachedDatabase.gameTable; + GameDaoManager get managers => GameDaoManager(this); +} + +class GameDaoManager { + final _$GameDaoMixin _db; + GameDaoManager(this._db); + $$GameTableTableTableManager get gameTable => + $$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable); } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 552b566..0d66ef6 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -3,8 +3,8 @@ import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/db/tables/group_table.dart'; import 'package:tallee/data/db/tables/match_table.dart'; import 'package:tallee/data/db/tables/player_group_table.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/player.dart'; part 'group_dao.g.dart'; diff --git a/lib/data/dao/group_dao.g.dart b/lib/data/dao/group_dao.g.dart index 37a5586..665c93c 100644 --- a/lib/data/dao/group_dao.g.dart +++ b/lib/data/dao/group_dao.g.dart @@ -10,4 +10,23 @@ mixin _$GroupDaoMixin on DatabaseAccessor { attachedDatabase.playerGroupTable; $GameTableTable get gameTable => attachedDatabase.gameTable; $MatchTableTable get matchTable => attachedDatabase.matchTable; + GroupDaoManager get managers => GroupDaoManager(this); +} + +class GroupDaoManager { + final _$GroupDaoMixin _db; + GroupDaoManager(this._db); + $$GroupTableTableTableManager get groupTable => + $$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable); + $$PlayerTableTableTableManager get playerTable => + $$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable); + $$PlayerGroupTableTableTableManager get playerGroupTable => + $$PlayerGroupTableTableTableManager( + _db.attachedDatabase, + _db.playerGroupTable, + ); + $$GameTableTableTableManager get gameTable => + $$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable); + $$MatchTableTableTableManager get matchTable => + $$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable); } diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index d3fd06f..b23930b 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -4,10 +4,10 @@ import 'package:tallee/data/db/tables/game_table.dart'; import 'package:tallee/data/db/tables/group_table.dart'; import 'package:tallee/data/db/tables/match_table.dart'; import 'package:tallee/data/db/tables/player_match_table.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.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/player.dart'; part 'match_dao.g.dart'; @@ -29,16 +29,20 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { } final players = await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; - final winner = await getWinner(matchId: row.id); + + final scores = await db.scoreDao.getAllMatchScores(matchId: row.id); + + final winner = await db.scoreDao.getWinner(matchId: row.id); return Match( id: row.id, - name: row.name ?? '', + name: row.name, game: game, group: group, players: players, notes: row.notes ?? '', createdAt: row.createdAt, endedAt: row.endedAt, + scores: scores, winner: winner, ); }), @@ -60,17 +64,20 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? []; - final winner = await getWinner(matchId: matchId); + final scores = await db.scoreDao.getAllMatchScores(matchId: matchId); + + final winner = await db.scoreDao.getWinner(matchId: matchId); return Match( id: result.id, - name: result.name ?? '', + name: result.name, game: game, group: group, players: players, notes: result.notes ?? '', createdAt: result.createdAt, endedAt: result.endedAt, + scores: scores, winner: winner, ); } @@ -85,7 +92,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { id: match.id, gameId: match.game.id, groupId: Value(match.group?.id), - name: Value(match.name), + name: match.name, notes: Value(match.notes), createdAt: match.createdAt, endedAt: Value(match.endedAt), @@ -100,8 +107,20 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } + for (final pid in match.scores.keys) { + final playerScores = match.scores[pid]!; + await db.scoreDao.addScoresAsList( + entrys: playerScores, + playerId: pid, + matchId: match.id, + ); + } + if (match.winner != null) { - await setWinner(matchId: match.id, winnerId: match.winner!.id); + await db.scoreDao.setWinner( + matchId: match.id, + playerId: match.winner!.id, + ); } }); } @@ -170,7 +189,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { id: match.id, gameId: match.game.id, groupId: Value(match.group?.id), - name: Value(match.name), + name: match.name, notes: Value(match.notes), createdAt: match.createdAt, endedAt: Value(match.endedAt), @@ -223,7 +242,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { PlayerMatchTableCompanion.insert( matchId: match.id, playerId: p.id, - score: 0, ), mode: InsertMode.insertOrIgnore, ); @@ -280,10 +298,10 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { final group = await db.groupDao.getGroupById(groupId: groupId); final players = await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; - final winner = await db.matchDao.getWinner(matchId: row.id); + final winner = await db.scoreDao.getWinner(matchId: row.id); return Match( id: row.id, - name: row.name ?? '', + name: row.name, game: game, group: group, players: players, @@ -437,91 +455,4 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); }); } - - // ============================================================ - // Winner methods - handle winner logic via player scores - // ============================================================ - - /// Checks if a match has a winner. - /// Returns true if any player in the match has their score set to 1. - Future hasWinner({required String matchId}) async { - final players = - await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? []; - - for (final player in players) { - final score = await db.playerMatchDao.getPlayerScore( - matchId: matchId, - playerId: player.id, - ); - if (score == 1) { - return true; - } - } - return false; - } - - /// Gets the winner of a match. - /// Returns the player with score 1, or null if no winner is set. - Future getWinner({required String matchId}) async { - final players = - await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? []; - - for (final player in players) { - final score = await db.playerMatchDao.getPlayerScore( - matchId: matchId, - playerId: player.id, - ); - if (score == 1) { - return player; - } - } - return null; - } - - /// Sets the winner of a match. - /// Sets all players' scores to 0, then sets the specified player's score to 1. - /// Returns `true` if the operation was successful, otherwise `false`. - Future setWinner({ - required String matchId, - required String winnerId, - }) async { - await db.transaction(() async { - final players = - await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? []; - - // Set all players' scores to 0 - for (final player in players) { - await db.playerMatchDao.updatePlayerScore( - matchId: matchId, - playerId: player.id, - newScore: 0, - ); - } - - // Set the winner's score to 1 - await db.playerMatchDao.updatePlayerScore( - matchId: matchId, - playerId: winnerId, - newScore: 1, - ); - }); - return true; - } - - /// Removes the winner of a match. - /// Sets the current winner's score to 0 (no winner). - /// Returns `true` if a winner was removed, otherwise `false`. - Future removeWinner({required String matchId}) async { - final winner = await getWinner(matchId: matchId); - if (winner == null) { - return false; - } - - final success = await db.playerMatchDao.updatePlayerScore( - matchId: matchId, - playerId: winner.id, - newScore: 0, - ); - return success; - } } diff --git a/lib/data/dao/match_dao.g.dart b/lib/data/dao/match_dao.g.dart index fa75fee..e3c7777 100644 --- a/lib/data/dao/match_dao.g.dart +++ b/lib/data/dao/match_dao.g.dart @@ -11,4 +11,25 @@ mixin _$MatchDaoMixin on DatabaseAccessor { $TeamTableTable get teamTable => attachedDatabase.teamTable; $PlayerMatchTableTable get playerMatchTable => attachedDatabase.playerMatchTable; + MatchDaoManager get managers => MatchDaoManager(this); +} + +class MatchDaoManager { + final _$MatchDaoMixin _db; + MatchDaoManager(this._db); + $$GameTableTableTableManager get gameTable => + $$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable); + $$GroupTableTableTableManager get groupTable => + $$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable); + $$MatchTableTableTableManager get matchTable => + $$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable); + $$PlayerTableTableTableManager get playerTable => + $$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable); + $$TeamTableTableTableManager get teamTable => + $$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable); + $$PlayerMatchTableTableTableManager get playerMatchTable => + $$PlayerMatchTableTableTableManager( + _db.attachedDatabase, + _db.playerMatchTable, + ); } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 2da9761..c58cb9a 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -1,7 +1,7 @@ import 'package:drift/drift.dart'; import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/db/tables/player_table.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/player.dart'; part 'player_dao.g.dart'; diff --git a/lib/data/dao/player_dao.g.dart b/lib/data/dao/player_dao.g.dart index c517581..9fefbf0 100644 --- a/lib/data/dao/player_dao.g.dart +++ b/lib/data/dao/player_dao.g.dart @@ -5,4 +5,12 @@ part of 'player_dao.dart'; // ignore_for_file: type=lint mixin _$PlayerDaoMixin on DatabaseAccessor { $PlayerTableTable get playerTable => attachedDatabase.playerTable; + PlayerDaoManager get managers => PlayerDaoManager(this); +} + +class PlayerDaoManager { + final _$PlayerDaoMixin _db; + PlayerDaoManager(this._db); + $$PlayerTableTableTableManager get playerTable => + $$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable); } diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index c3889c8..9411486 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -2,7 +2,7 @@ import 'package:drift/drift.dart'; import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/db/tables/player_group_table.dart'; import 'package:tallee/data/db/tables/player_table.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/player.dart'; part 'player_group_dao.g.dart'; diff --git a/lib/data/dao/player_group_dao.g.dart b/lib/data/dao/player_group_dao.g.dart index d54f979..ef7e7e3 100644 --- a/lib/data/dao/player_group_dao.g.dart +++ b/lib/data/dao/player_group_dao.g.dart @@ -8,4 +8,19 @@ mixin _$PlayerGroupDaoMixin on DatabaseAccessor { $GroupTableTable get groupTable => attachedDatabase.groupTable; $PlayerGroupTableTable get playerGroupTable => attachedDatabase.playerGroupTable; + PlayerGroupDaoManager get managers => PlayerGroupDaoManager(this); +} + +class PlayerGroupDaoManager { + final _$PlayerGroupDaoMixin _db; + PlayerGroupDaoManager(this._db); + $$PlayerTableTableTableManager get playerTable => + $$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable); + $$GroupTableTableTableManager get groupTable => + $$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable); + $$PlayerGroupTableTableTableManager get playerGroupTable => + $$PlayerGroupTableTableTableManager( + _db.attachedDatabase, + _db.playerGroupTable, + ); } diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index 48bf282..36a7dbe 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -2,7 +2,7 @@ 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/dto/player.dart'; +import 'package:tallee/data/models/player.dart'; part 'player_match_dao.g.dart'; @@ -17,14 +17,12 @@ class PlayerMatchDao extends DatabaseAccessor required String matchId, required String playerId, String? teamId, - int score = 0, }) async { await into(playerMatchTable).insert( PlayerMatchTableCompanion.insert( playerId: playerId, matchId: matchId, teamId: Value(teamId), - score: score, ), mode: InsertMode.insertOrIgnore, ); @@ -40,41 +38,12 @@ class PlayerMatchDao extends DatabaseAccessor if (result.isEmpty) return null; final futures = result.map( - (row) => db.playerDao.getPlayerById(playerId: row.playerId), + (row) => db.playerDao.getPlayerById(playerId: row.playerId), ); final players = await Future.wait(futures); return players; } - /// Retrieves a player's score for a specific match. - /// Returns null if the player is not in the match. - Future getPlayerScore({ - required String matchId, - required String playerId, - }) async { - final result = await (select(playerMatchTable) - ..where( - (p) => p.matchId.equals(matchId) & p.playerId.equals(playerId), - )) - .getSingleOrNull(); - return result?.score; - } - - /// Updates the score for a player in a match. - /// Returns `true` if the update was successful, otherwise `false`. - Future updatePlayerScore({ - required String matchId, - required String playerId, - required int newScore, - }) async { - final rowsAffected = await (update(playerMatchTable) - ..where( - (p) => p.matchId.equals(matchId) & p.playerId.equals(playerId), - )) - .write(PlayerMatchTableCompanion(score: Value(newScore))); - return rowsAffected > 0; - } - /// Updates the team for a player in a match. /// Returns `true` if the update was successful, otherwise `false`. Future updatePlayerTeam({ @@ -82,11 +51,11 @@ class PlayerMatchDao extends DatabaseAccessor 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))); + final rowsAffected = + await (update(playerMatchTable)..where( + (p) => p.matchId.equals(matchId) & p.playerId.equals(playerId), + )) + .write(PlayerMatchTableCompanion(teamId: Value(teamId))); return rowsAffected > 0; } @@ -94,11 +63,11 @@ class PlayerMatchDao extends DatabaseAccessor /// Returns `true` if there are players, otherwise `false`. Future matchHasPlayers({required String matchId}) async { final count = - await (selectOnly(playerMatchTable) - ..where(playerMatchTable.matchId.equals(matchId)) - ..addColumns([playerMatchTable.playerId.count()])) - .map((row) => row.read(playerMatchTable.playerId.count())) - .getSingle(); + await (selectOnly(playerMatchTable) + ..where(playerMatchTable.matchId.equals(matchId)) + ..addColumns([playerMatchTable.playerId.count()])) + .map((row) => row.read(playerMatchTable.playerId.count())) + .getSingle(); return (count ?? 0) > 0; } @@ -109,12 +78,12 @@ class PlayerMatchDao extends DatabaseAccessor required String playerId, }) async { final count = - await (selectOnly(playerMatchTable) - ..where(playerMatchTable.matchId.equals(matchId)) - ..where(playerMatchTable.playerId.equals(playerId)) - ..addColumns([playerMatchTable.playerId.count()])) - .map((row) => row.read(playerMatchTable.playerId.count())) - .getSingle(); + await (selectOnly(playerMatchTable) + ..where(playerMatchTable.matchId.equals(matchId)) + ..where(playerMatchTable.playerId.equals(playerId)) + ..addColumns([playerMatchTable.playerId.count()])) + .map((row) => row.read(playerMatchTable.playerId.count())) + .getSingle(); return (count ?? 0) > 0; } @@ -153,9 +122,9 @@ class PlayerMatchDao extends DatabaseAccessor if (playersToRemove.isNotEmpty) { await (delete(playerMatchTable)..where( (pg) => - pg.matchId.equals(matchId) & - pg.playerId.isIn(playersToRemove.toList()), - )) + pg.matchId.equals(matchId) & + pg.playerId.isIn(playersToRemove.toList()), + )) .go(); } @@ -164,15 +133,14 @@ class PlayerMatchDao extends DatabaseAccessor final inserts = playersToAdd .map( (id) => PlayerMatchTableCompanion.insert( - playerId: id, - matchId: matchId, - score: 0, - ), - ) + playerId: id, + matchId: matchId, + ), + ) .toList(); await Future.wait( inserts.map( - (c) => into( + (c) => into( playerMatchTable, ).insert(c, mode: InsertMode.insertOrIgnore), ), @@ -186,16 +154,14 @@ class PlayerMatchDao extends DatabaseAccessor required String matchId, required String teamId, }) async { - final result = await (select(playerMatchTable) - ..where( - (p) => p.matchId.equals(matchId) & p.teamId.equals(teamId), - )) - .get(); + final result = await (select( + playerMatchTable, + )..where((p) => p.matchId.equals(matchId) & p.teamId.equals(teamId))).get(); if (result.isEmpty) return []; final futures = result.map( - (row) => db.playerDao.getPlayerById(playerId: row.playerId), + (row) => db.playerDao.getPlayerById(playerId: row.playerId), ); return Future.wait(futures); } diff --git a/lib/data/dao/player_match_dao.g.dart b/lib/data/dao/player_match_dao.g.dart index 4c8bcbe..9ee099a 100644 --- a/lib/data/dao/player_match_dao.g.dart +++ b/lib/data/dao/player_match_dao.g.dart @@ -11,4 +11,25 @@ mixin _$PlayerMatchDaoMixin on DatabaseAccessor { $TeamTableTable get teamTable => attachedDatabase.teamTable; $PlayerMatchTableTable get playerMatchTable => attachedDatabase.playerMatchTable; + PlayerMatchDaoManager get managers => PlayerMatchDaoManager(this); +} + +class PlayerMatchDaoManager { + final _$PlayerMatchDaoMixin _db; + PlayerMatchDaoManager(this._db); + $$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); + $$TeamTableTableTableManager get teamTable => + $$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable); + $$PlayerMatchTableTableTableManager get playerMatchTable => + $$PlayerMatchTableTableTableManager( + _db.attachedDatabase, + _db.playerMatchTable, + ); } diff --git a/lib/data/dao/score_dao.dart b/lib/data/dao/score_dao.dart index 04e8fcf..34d3e97 100644 --- a/lib/data/dao/score_dao.dart +++ b/lib/data/dao/score_dao.dart @@ -1,27 +1,14 @@ +import 'dart:async'; + import 'package:drift/drift.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/db/tables/score_table.dart'; +import 'package:tallee/data/db/tables/score_entry_table.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/score_entry.dart'; part 'score_dao.g.dart'; -/// A data class representing a score entry. -class ScoreEntry { - final String playerId; - final String matchId; - final int roundNumber; - final int score; - final int change; - - ScoreEntry({ - required this.playerId, - required this.matchId, - required this.roundNumber, - required this.score, - required this.change, - }); -} - -@DriftAccessor(tables: [ScoreTable]) +@DriftAccessor(tables: [ScoreEntryTable]) class ScoreDao extends DatabaseAccessor with _$ScoreDaoMixin { ScoreDao(super.db); @@ -29,108 +16,134 @@ class ScoreDao extends DatabaseAccessor with _$ScoreDaoMixin { Future addScore({ required String playerId, required String matchId, - required int roundNumber, - required int score, - required int change, + required ScoreEntry entry, }) async { - await into(scoreTable).insert( - ScoreTableCompanion.insert( + await into(scoreEntryTable).insert( + ScoreEntryTableCompanion.insert( playerId: playerId, matchId: matchId, - roundNumber: roundNumber, - score: score, - change: change, + roundNumber: entry.roundNumber, + score: entry.score, + change: entry.change, ), mode: InsertMode.insertOrReplace, ); } - /// Retrieves all scores for a specific match. - Future> getScoresForMatch({required String matchId}) async { - final query = select(scoreTable)..where((s) => s.matchId.equals(matchId)); - final result = await query.get(); - return result - .map( - (row) => ScoreEntry( - playerId: row.playerId, - matchId: row.matchId, - roundNumber: row.roundNumber, - score: row.score, - change: row.change, - ), - ) - .toList(); - } - - /// Retrieves all scores for a specific player in a match. - Future> getPlayerScoresInMatch({ + Future addScoresAsList({ + required List entrys, required String playerId, required String matchId, }) async { - final query = select(scoreTable) - ..where( - (s) => s.playerId.equals(playerId) & s.matchId.equals(matchId), - ) - ..orderBy([(s) => OrderingTerm.asc(s.roundNumber)]); - final result = await query.get(); - return result + if (entrys.isEmpty) return; + final entries = entrys .map( - (row) => ScoreEntry( - playerId: row.playerId, - matchId: row.matchId, - roundNumber: row.roundNumber, - score: row.score, - change: row.change, + (score) => ScoreEntryTableCompanion.insert( + playerId: playerId, + matchId: matchId, + roundNumber: score.roundNumber, + score: score.score, + change: score.change, ), ) .toList(); + + await batch((batch) { + batch.insertAll( + scoreEntryTable, + entries, + mode: InsertMode.insertOrReplace, + ); + }); } /// Retrieves the score for a specific round. - Future getScoreForRound({ + Future getScore({ required String playerId, required String matchId, - required int roundNumber, + int roundNumber = 0, }) async { - final query = select(scoreTable) + final query = select(scoreEntryTable) ..where( (s) => s.playerId.equals(playerId) & s.matchId.equals(matchId) & s.roundNumber.equals(roundNumber), ); + final result = await query.getSingleOrNull(); if (result == null) return null; + return ScoreEntry( - playerId: result.playerId, - matchId: result.matchId, roundNumber: result.roundNumber, score: result.score, change: result.change, ); } + /// Retrieves all scores for a specific match. + Future>> getAllMatchScores({ + required String matchId, + }) async { + final query = select(scoreEntryTable) + ..where((s) => s.matchId.equals(matchId)); + final result = await query.get(); + + final Map> scoresByPlayer = {}; + for (final row in result) { + final score = ScoreEntry( + roundNumber: row.roundNumber, + score: row.score, + change: row.change, + ); + scoresByPlayer.putIfAbsent(row.playerId, () => []).add(score); + } + + return scoresByPlayer; + } + + /// Retrieves all scores for a specific player in a match. + Future> getAllPlayerScoresInMatch({ + required String playerId, + required String matchId, + }) async { + final query = select(scoreEntryTable) + ..where((s) => s.playerId.equals(playerId) & s.matchId.equals(matchId)) + ..orderBy([(s) => OrderingTerm.asc(s.roundNumber)]); + final result = await query.get(); + return result + .map( + (row) => ScoreEntry( + roundNumber: row.roundNumber, + score: row.score, + change: row.change, + ), + ) + .toList() + ..sort( + (scoreA, scoreB) => scoreA.roundNumber.compareTo(scoreB.roundNumber), + ); + } + /// Updates a score entry. Future updateScore({ required String playerId, required String matchId, - required int roundNumber, - required int newScore, - required int newChange, + required ScoreEntry newEntry, }) async { - final rowsAffected = await (update(scoreTable) - ..where( - (s) => - s.playerId.equals(playerId) & - s.matchId.equals(matchId) & - s.roundNumber.equals(roundNumber), - )) - .write( - ScoreTableCompanion( - score: Value(newScore), - change: Value(newChange), - ), - ); + final rowsAffected = + await (update(scoreEntryTable)..where( + (s) => + s.playerId.equals(playerId) & + s.matchId.equals(matchId) & + s.roundNumber.equals(newEntry.roundNumber), + )) + .write( + ScoreEntryTableCompanion( + score: Value(newEntry.score), + change: Value(newEntry.change), + ), + ); return rowsAffected > 0; } @@ -138,9 +151,9 @@ class ScoreDao extends DatabaseAccessor with _$ScoreDaoMixin { Future deleteScore({ required String playerId, required String matchId, - required int roundNumber, + int roundNumber = 0, }) async { - final query = delete(scoreTable) + final query = delete(scoreEntryTable) ..where( (s) => s.playerId.equals(playerId) & @@ -151,41 +164,164 @@ class ScoreDao extends DatabaseAccessor with _$ScoreDaoMixin { return rowsAffected > 0; } - /// Deletes all scores for a specific match. - Future deleteScoresForMatch({required String matchId}) async { - final query = delete(scoreTable)..where((s) => s.matchId.equals(matchId)); + Future deleteAllScoresForMatch({required String matchId}) async { + final query = delete(scoreEntryTable) + ..where((s) => s.matchId.equals(matchId)); final rowsAffected = await query.go(); return rowsAffected > 0; } - /// Deletes all scores for a specific player. - Future deleteScoresForPlayer({required String playerId}) async { - final query = delete(scoreTable)..where((s) => s.playerId.equals(playerId)); + Future deleteAllScoresForPlayerInMatch({ + required String matchId, + required String playerId, + }) async { + final query = delete(scoreEntryTable) + ..where((s) => s.playerId.equals(playerId) & s.matchId.equals(matchId)); final rowsAffected = await query.go(); return rowsAffected > 0; } - /// Gets the latest round number for a match. - Future getLatestRoundNumber({required String matchId}) async { - final query = selectOnly(scoreTable) - ..where(scoreTable.matchId.equals(matchId)) - ..addColumns([scoreTable.roundNumber.max()]); + /// Gets the highest (latest) round number for a match. + /// Returns `null` if there are no scores for the match. + Future 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(scoreTable.roundNumber.max()) ?? 0; + return result.read(scoreEntryTable.roundNumber.max()); } - /// Gets the total score for a player in a match (sum of all changes). + /// 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 getTotalScoreForPlayer({ required String playerId, required String matchId, }) async { - final scores = await getPlayerScoresInMatch( + final scores = await getAllPlayerScoresInMatch( playerId: playerId, matchId: matchId, ); if (scores.isEmpty) return 0; - // Return the score from the latest round - return scores.last.score; + // Return the sum of all score changes + return scores.fold(0, (sum, element) => sum + element.change); + } + + Future hasWinner({required String matchId}) async { + return await getWinner(matchId: matchId) != null; + } + + // Setting the winner for a game and clearing previous winner if exists. + Future setWinner({ + required String matchId, + required String playerId, + }) async { + // Clear previous winner if exists + deleteAllScoresForMatch(matchId: matchId); + + // Set the winner's score to 1 + final rowsAffected = await into(scoreEntryTable).insert( + ScoreEntryTableCompanion.insert( + playerId: playerId, + matchId: matchId, + roundNumber: 0, + score: 1, + change: 0, + ), + mode: InsertMode.insertOrReplace, + ); + + return rowsAffected > 0; + } + + // Retrieves the winner of a match based on the highest score. + Future getWinner({required String matchId}) async { + final query = select(scoreEntryTable) + ..where((s) => s.matchId.equals(matchId)) + ..orderBy([(s) => OrderingTerm.desc(s.score)]) + ..limit(1); + final result = await query.getSingleOrNull(); + + if (result == null) return null; + + final player = await db.playerDao.getPlayerById(playerId: result.playerId); + return Player( + id: player.id, + name: player.name, + createdAt: player.createdAt, + description: player.description, + ); + } + + /// Removes the winner of a match. + /// + /// Returns `true` if the winner was removed, `false` if there are multiple + /// scores or if the winner cannot be removed. + Future removeWinner({required String matchId}) async { + final scores = await getAllMatchScores(matchId: matchId); + + if (scores.length > 1) { + return false; + } else { + return await deleteAllScoresForMatch(matchId: matchId); + } + } + + Future hasLooser({required String matchId}) async { + return await getLooser(matchId: matchId) != null; + } + + // Setting the looser for a game and clearing previous looser if exists. + Future setLooser({ + required String matchId, + required String playerId, + }) async { + // Clear previous loosers if exists + deleteAllScoresForMatch(matchId: matchId); + + // Set the loosers score to 0 + final rowsAffected = await into(scoreEntryTable).insert( + ScoreEntryTableCompanion.insert( + playerId: playerId, + matchId: matchId, + roundNumber: 0, + score: 0, + change: 0, + ), + mode: InsertMode.insertOrReplace, + ); + + return rowsAffected > 0; + } + + /// Retrieves the looser of a match based on the score 0. + Future getLooser({required String matchId}) async { + final query = select(scoreEntryTable) + ..where((s) => s.matchId.equals(matchId) & s.score.equals(0)); + final result = await query.getSingleOrNull(); + + if (result == null) return null; + + final player = await db.playerDao.getPlayerById(playerId: result.playerId); + return Player( + id: player.id, + name: player.name, + createdAt: player.createdAt, + description: player.description, + ); + } + + /// Removes the looser of a match. + /// + /// Returns `true` if the looser was removed, `false` if there are multiple + /// scores or if the looser cannot be removed. + Future removeLooser({required String matchId}) async { + final scores = await getAllMatchScores(matchId: matchId); + + if (scores.length > 1) { + return false; + } else { + return await deleteAllScoresForMatch(matchId: matchId); + } } } - diff --git a/lib/data/dao/score_dao.g.dart b/lib/data/dao/score_dao.g.dart index 1f4e367..451075f 100644 --- a/lib/data/dao/score_dao.g.dart +++ b/lib/data/dao/score_dao.g.dart @@ -8,5 +8,24 @@ mixin _$ScoreDaoMixin on DatabaseAccessor { $GameTableTable get gameTable => attachedDatabase.gameTable; $GroupTableTable get groupTable => attachedDatabase.groupTable; $MatchTableTable get matchTable => attachedDatabase.matchTable; - $ScoreTableTable get scoreTable => attachedDatabase.scoreTable; + $ScoreEntryTableTable get scoreEntryTable => attachedDatabase.scoreEntryTable; + ScoreDaoManager get managers => ScoreDaoManager(this); +} + +class ScoreDaoManager { + final _$ScoreDaoMixin _db; + ScoreDaoManager(this._db); + $$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); + $$ScoreEntryTableTableTableManager get scoreEntryTable => + $$ScoreEntryTableTableTableManager( + _db.attachedDatabase, + _db.scoreEntryTable, + ); } diff --git a/lib/data/dao/team_dao.dart b/lib/data/dao/team_dao.dart index 5c2aadb..01dc724 100644 --- a/lib/data/dao/team_dao.dart +++ b/lib/data/dao/team_dao.dart @@ -1,8 +1,8 @@ import 'package:drift/drift.dart'; import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/db/tables/team_table.dart'; -import 'package:tallee/data/dto/player.dart'; -import 'package:tallee/data/dto/team.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/team.dart'; part 'team_dao.g.dart'; @@ -144,4 +144,3 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { return rowsAffected > 0; } } - diff --git a/lib/data/dao/team_dao.g.dart b/lib/data/dao/team_dao.g.dart index 1bf5b21..3b78c03 100644 --- a/lib/data/dao/team_dao.g.dart +++ b/lib/data/dao/team_dao.g.dart @@ -5,4 +5,12 @@ part of 'team_dao.dart'; // ignore_for_file: type=lint mixin _$TeamDaoMixin on DatabaseAccessor { $TeamTableTable get teamTable => attachedDatabase.teamTable; + TeamDaoManager get managers => TeamDaoManager(this); +} + +class TeamDaoManager { + final _$TeamDaoMixin _db; + TeamDaoManager(this._db); + $$TeamTableTableTableManager get teamTable => + $$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable); } diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index 4ab053e..9e96eb3 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -15,7 +15,7 @@ import 'package:tallee/data/db/tables/match_table.dart'; import 'package:tallee/data/db/tables/player_group_table.dart'; import 'package:tallee/data/db/tables/player_match_table.dart'; import 'package:tallee/data/db/tables/player_table.dart'; -import 'package:tallee/data/db/tables/score_table.dart'; +import 'package:tallee/data/db/tables/score_entry_table.dart'; import 'package:tallee/data/db/tables/team_table.dart'; part 'database.g.dart'; @@ -29,7 +29,7 @@ part 'database.g.dart'; PlayerMatchTable, GameTable, TeamTable, - ScoreTable, + ScoreEntryTable, ], daos: [ PlayerDao, @@ -39,7 +39,7 @@ part 'database.g.dart'; PlayerMatchDao, GameDao, ScoreDao, - TeamDao + TeamDao, ], ) class AppDatabase extends _$AppDatabase { @@ -60,7 +60,9 @@ class AppDatabase extends _$AppDatabase { static QueryExecutor _openConnection() { return driftDatabase( name: 'gametracker_db', - native: const DriftNativeOptions(databaseDirectory: getApplicationSupportDirectory), + native: const DriftNativeOptions( + databaseDirectory: getApplicationSupportDirectory, + ), ); } } diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 227c7c0..8c0bd02 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -1131,9 +1131,9 @@ class $MatchTableTable extends MatchTable late final GeneratedColumn name = GeneratedColumn( 'name', aliasedName, - true, + false, type: DriftSqlType.string, - requiredDuringInsert: false, + requiredDuringInsert: true, ); static const VerificationMeta _notesMeta = const VerificationMeta('notes'); @override @@ -1212,6 +1212,8 @@ class $MatchTableTable extends MatchTable _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta), ); + } else if (isInserting) { + context.missing(_nameMeta); } if (data.containsKey('notes')) { context.handle( @@ -1257,7 +1259,7 @@ class $MatchTableTable extends MatchTable name: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}name'], - ), + )!, notes: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}notes'], @@ -1283,7 +1285,7 @@ class MatchTableData extends DataClass implements Insertable { final String id; final String gameId; final String? groupId; - final String? name; + final String name; final String? notes; final DateTime createdAt; final DateTime? endedAt; @@ -1291,7 +1293,7 @@ class MatchTableData extends DataClass implements Insertable { required this.id, required this.gameId, this.groupId, - this.name, + required this.name, this.notes, required this.createdAt, this.endedAt, @@ -1304,9 +1306,7 @@ class MatchTableData extends DataClass implements Insertable { if (!nullToAbsent || groupId != null) { map['group_id'] = Variable(groupId); } - if (!nullToAbsent || name != null) { - map['name'] = Variable(name); - } + map['name'] = Variable(name); if (!nullToAbsent || notes != null) { map['notes'] = Variable(notes); } @@ -1324,7 +1324,7 @@ class MatchTableData extends DataClass implements Insertable { groupId: groupId == null && nullToAbsent ? const Value.absent() : Value(groupId), - name: name == null && nullToAbsent ? const Value.absent() : Value(name), + name: Value(name), notes: notes == null && nullToAbsent ? const Value.absent() : Value(notes), @@ -1344,7 +1344,7 @@ class MatchTableData extends DataClass implements Insertable { id: serializer.fromJson(json['id']), gameId: serializer.fromJson(json['gameId']), groupId: serializer.fromJson(json['groupId']), - name: serializer.fromJson(json['name']), + name: serializer.fromJson(json['name']), notes: serializer.fromJson(json['notes']), createdAt: serializer.fromJson(json['createdAt']), endedAt: serializer.fromJson(json['endedAt']), @@ -1357,7 +1357,7 @@ class MatchTableData extends DataClass implements Insertable { 'id': serializer.toJson(id), 'gameId': serializer.toJson(gameId), 'groupId': serializer.toJson(groupId), - 'name': serializer.toJson(name), + 'name': serializer.toJson(name), 'notes': serializer.toJson(notes), 'createdAt': serializer.toJson(createdAt), 'endedAt': serializer.toJson(endedAt), @@ -1368,7 +1368,7 @@ class MatchTableData extends DataClass implements Insertable { String? id, String? gameId, Value groupId = const Value.absent(), - Value name = const Value.absent(), + String? name, Value notes = const Value.absent(), DateTime? createdAt, Value endedAt = const Value.absent(), @@ -1376,7 +1376,7 @@ class MatchTableData extends DataClass implements Insertable { id: id ?? this.id, gameId: gameId ?? this.gameId, groupId: groupId.present ? groupId.value : this.groupId, - name: name.present ? name.value : this.name, + name: name ?? this.name, notes: notes.present ? notes.value : this.notes, createdAt: createdAt ?? this.createdAt, endedAt: endedAt.present ? endedAt.value : this.endedAt, @@ -1427,7 +1427,7 @@ class MatchTableCompanion extends UpdateCompanion { final Value id; final Value gameId; final Value groupId; - final Value name; + final Value name; final Value notes; final Value createdAt; final Value endedAt; @@ -1446,13 +1446,14 @@ class MatchTableCompanion extends UpdateCompanion { required String id, required String gameId, this.groupId = const Value.absent(), - this.name = const Value.absent(), + required String name, this.notes = const Value.absent(), required DateTime createdAt, this.endedAt = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), gameId = Value(gameId), + name = Value(name), createdAt = Value(createdAt); static Insertable custom({ Expression? id, @@ -1480,7 +1481,7 @@ class MatchTableCompanion extends UpdateCompanion { Value? id, Value? gameId, Value? groupId, - Value? name, + Value? name, Value? notes, Value? createdAt, Value? endedAt, @@ -2074,17 +2075,8 @@ class $PlayerMatchTableTable extends PlayerMatchTable 'REFERENCES team_table (id)', ), ); - static const VerificationMeta _scoreMeta = const VerificationMeta('score'); @override - late final GeneratedColumn score = GeneratedColumn( - 'score', - aliasedName, - false, - type: DriftSqlType.int, - requiredDuringInsert: true, - ); - @override - List get $columns => [playerId, matchId, teamId, score]; + List get $columns => [playerId, matchId, teamId]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -2119,14 +2111,6 @@ class $PlayerMatchTableTable extends PlayerMatchTable teamId.isAcceptableOrUnknown(data['team_id']!, _teamIdMeta), ); } - if (data.containsKey('score')) { - context.handle( - _scoreMeta, - score.isAcceptableOrUnknown(data['score']!, _scoreMeta), - ); - } else if (isInserting) { - context.missing(_scoreMeta); - } return context; } @@ -2148,10 +2132,6 @@ class $PlayerMatchTableTable extends PlayerMatchTable DriftSqlType.string, data['${effectivePrefix}team_id'], ), - score: attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}score'], - )!, ); } @@ -2166,12 +2146,10 @@ class PlayerMatchTableData extends DataClass final String playerId; final String matchId; final String? teamId; - final int score; const PlayerMatchTableData({ required this.playerId, required this.matchId, this.teamId, - required this.score, }); @override Map toColumns(bool nullToAbsent) { @@ -2181,7 +2159,6 @@ class PlayerMatchTableData extends DataClass if (!nullToAbsent || teamId != null) { map['team_id'] = Variable(teamId); } - map['score'] = Variable(score); return map; } @@ -2192,7 +2169,6 @@ class PlayerMatchTableData extends DataClass teamId: teamId == null && nullToAbsent ? const Value.absent() : Value(teamId), - score: Value(score), ); } @@ -2205,7 +2181,6 @@ class PlayerMatchTableData extends DataClass playerId: serializer.fromJson(json['playerId']), matchId: serializer.fromJson(json['matchId']), teamId: serializer.fromJson(json['teamId']), - score: serializer.fromJson(json['score']), ); } @override @@ -2215,7 +2190,6 @@ class PlayerMatchTableData extends DataClass 'playerId': serializer.toJson(playerId), 'matchId': serializer.toJson(matchId), 'teamId': serializer.toJson(teamId), - 'score': serializer.toJson(score), }; } @@ -2223,19 +2197,16 @@ class PlayerMatchTableData extends DataClass String? playerId, String? matchId, Value teamId = const Value.absent(), - int? score, }) => PlayerMatchTableData( playerId: playerId ?? this.playerId, matchId: matchId ?? this.matchId, teamId: teamId.present ? teamId.value : this.teamId, - score: score ?? this.score, ); PlayerMatchTableData copyWithCompanion(PlayerMatchTableCompanion data) { return PlayerMatchTableData( playerId: data.playerId.present ? data.playerId.value : this.playerId, matchId: data.matchId.present ? data.matchId.value : this.matchId, teamId: data.teamId.present ? data.teamId.value : this.teamId, - score: data.score.present ? data.score.value : this.score, ); } @@ -2244,58 +2215,50 @@ class PlayerMatchTableData extends DataClass return (StringBuffer('PlayerMatchTableData(') ..write('playerId: $playerId, ') ..write('matchId: $matchId, ') - ..write('teamId: $teamId, ') - ..write('score: $score') + ..write('teamId: $teamId') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(playerId, matchId, teamId, score); + int get hashCode => Object.hash(playerId, matchId, teamId); @override bool operator ==(Object other) => identical(this, other) || (other is PlayerMatchTableData && other.playerId == this.playerId && other.matchId == this.matchId && - other.teamId == this.teamId && - other.score == this.score); + other.teamId == this.teamId); } class PlayerMatchTableCompanion extends UpdateCompanion { final Value playerId; final Value matchId; final Value teamId; - final Value score; final Value rowid; const PlayerMatchTableCompanion({ this.playerId = const Value.absent(), this.matchId = const Value.absent(), this.teamId = const Value.absent(), - this.score = const Value.absent(), this.rowid = const Value.absent(), }); PlayerMatchTableCompanion.insert({ required String playerId, required String matchId, this.teamId = const Value.absent(), - required int score, this.rowid = const Value.absent(), }) : playerId = Value(playerId), - matchId = Value(matchId), - score = Value(score); + matchId = Value(matchId); static Insertable custom({ Expression? playerId, Expression? matchId, Expression? teamId, - Expression? score, Expression? rowid, }) { return RawValuesInsertable({ if (playerId != null) 'player_id': playerId, if (matchId != null) 'match_id': matchId, if (teamId != null) 'team_id': teamId, - if (score != null) 'score': score, if (rowid != null) 'rowid': rowid, }); } @@ -2304,14 +2267,12 @@ class PlayerMatchTableCompanion extends UpdateCompanion { Value? playerId, Value? matchId, Value? teamId, - Value? score, Value? rowid, }) { return PlayerMatchTableCompanion( playerId: playerId ?? this.playerId, matchId: matchId ?? this.matchId, teamId: teamId ?? this.teamId, - score: score ?? this.score, rowid: rowid ?? this.rowid, ); } @@ -2328,9 +2289,6 @@ class PlayerMatchTableCompanion extends UpdateCompanion { if (teamId.present) { map['team_id'] = Variable(teamId.value); } - if (score.present) { - map['score'] = Variable(score.value); - } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -2343,19 +2301,18 @@ class PlayerMatchTableCompanion extends UpdateCompanion { ..write('playerId: $playerId, ') ..write('matchId: $matchId, ') ..write('teamId: $teamId, ') - ..write('score: $score, ') ..write('rowid: $rowid') ..write(')')) .toString(); } } -class $ScoreTableTable extends ScoreTable - with TableInfo<$ScoreTableTable, ScoreTableData> { +class $ScoreEntryTableTable extends ScoreEntryTable + with TableInfo<$ScoreEntryTableTable, ScoreEntryTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $ScoreTableTable(this.attachedDatabase, [this._alias]); + $ScoreEntryTableTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _playerIdMeta = const VerificationMeta( 'playerId', ); @@ -2425,10 +2382,10 @@ class $ScoreTableTable extends ScoreTable String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'score_table'; + static const String $name = 'score_entry_table'; @override VerificationContext validateIntegrity( - Insertable instance, { + Insertable instance, { bool isInserting = false, }) { final context = VerificationContext(); @@ -2482,9 +2439,9 @@ class $ScoreTableTable extends ScoreTable @override Set get $primaryKey => {playerId, matchId, roundNumber}; @override - ScoreTableData map(Map data, {String? tablePrefix}) { + ScoreEntryTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return ScoreTableData( + return ScoreEntryTableData( playerId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}player_id'], @@ -2509,18 +2466,19 @@ class $ScoreTableTable extends ScoreTable } @override - $ScoreTableTable createAlias(String alias) { - return $ScoreTableTable(attachedDatabase, alias); + $ScoreEntryTableTable createAlias(String alias) { + return $ScoreEntryTableTable(attachedDatabase, alias); } } -class ScoreTableData extends DataClass implements Insertable { +class ScoreEntryTableData extends DataClass + implements Insertable { final String playerId; final String matchId; final int roundNumber; final int score; final int change; - const ScoreTableData({ + const ScoreEntryTableData({ required this.playerId, required this.matchId, required this.roundNumber, @@ -2538,8 +2496,8 @@ class ScoreTableData extends DataClass implements Insertable { return map; } - ScoreTableCompanion toCompanion(bool nullToAbsent) { - return ScoreTableCompanion( + ScoreEntryTableCompanion toCompanion(bool nullToAbsent) { + return ScoreEntryTableCompanion( playerId: Value(playerId), matchId: Value(matchId), roundNumber: Value(roundNumber), @@ -2548,12 +2506,12 @@ class ScoreTableData extends DataClass implements Insertable { ); } - factory ScoreTableData.fromJson( + factory ScoreEntryTableData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return ScoreTableData( + return ScoreEntryTableData( playerId: serializer.fromJson(json['playerId']), matchId: serializer.fromJson(json['matchId']), roundNumber: serializer.fromJson(json['roundNumber']), @@ -2573,21 +2531,21 @@ class ScoreTableData extends DataClass implements Insertable { }; } - ScoreTableData copyWith({ + ScoreEntryTableData copyWith({ String? playerId, String? matchId, int? roundNumber, int? score, int? change, - }) => ScoreTableData( + }) => ScoreEntryTableData( playerId: playerId ?? this.playerId, matchId: matchId ?? this.matchId, roundNumber: roundNumber ?? this.roundNumber, score: score ?? this.score, change: change ?? this.change, ); - ScoreTableData copyWithCompanion(ScoreTableCompanion data) { - return ScoreTableData( + ScoreEntryTableData copyWithCompanion(ScoreEntryTableCompanion data) { + return ScoreEntryTableData( playerId: data.playerId.present ? data.playerId.value : this.playerId, matchId: data.matchId.present ? data.matchId.value : this.matchId, roundNumber: data.roundNumber.present @@ -2600,7 +2558,7 @@ class ScoreTableData extends DataClass implements Insertable { @override String toString() { - return (StringBuffer('ScoreTableData(') + return (StringBuffer('ScoreEntryTableData(') ..write('playerId: $playerId, ') ..write('matchId: $matchId, ') ..write('roundNumber: $roundNumber, ') @@ -2616,7 +2574,7 @@ class ScoreTableData extends DataClass implements Insertable { @override bool operator ==(Object other) => identical(this, other) || - (other is ScoreTableData && + (other is ScoreEntryTableData && other.playerId == this.playerId && other.matchId == this.matchId && other.roundNumber == this.roundNumber && @@ -2624,14 +2582,14 @@ class ScoreTableData extends DataClass implements Insertable { other.change == this.change); } -class ScoreTableCompanion extends UpdateCompanion { +class ScoreEntryTableCompanion extends UpdateCompanion { final Value playerId; final Value matchId; final Value roundNumber; final Value score; final Value change; final Value rowid; - const ScoreTableCompanion({ + const ScoreEntryTableCompanion({ this.playerId = const Value.absent(), this.matchId = const Value.absent(), this.roundNumber = const Value.absent(), @@ -2639,7 +2597,7 @@ class ScoreTableCompanion extends UpdateCompanion { this.change = const Value.absent(), this.rowid = const Value.absent(), }); - ScoreTableCompanion.insert({ + ScoreEntryTableCompanion.insert({ required String playerId, required String matchId, required int roundNumber, @@ -2651,7 +2609,7 @@ class ScoreTableCompanion extends UpdateCompanion { roundNumber = Value(roundNumber), score = Value(score), change = Value(change); - static Insertable custom({ + static Insertable custom({ Expression? playerId, Expression? matchId, Expression? roundNumber, @@ -2669,7 +2627,7 @@ class ScoreTableCompanion extends UpdateCompanion { }); } - ScoreTableCompanion copyWith({ + ScoreEntryTableCompanion copyWith({ Value? playerId, Value? matchId, Value? roundNumber, @@ -2677,7 +2635,7 @@ class ScoreTableCompanion extends UpdateCompanion { Value? change, Value? rowid, }) { - return ScoreTableCompanion( + return ScoreEntryTableCompanion( playerId: playerId ?? this.playerId, matchId: matchId ?? this.matchId, roundNumber: roundNumber ?? this.roundNumber, @@ -2713,7 +2671,7 @@ class ScoreTableCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('ScoreTableCompanion(') + return (StringBuffer('ScoreEntryTableCompanion(') ..write('playerId: $playerId, ') ..write('matchId: $matchId, ') ..write('roundNumber: $roundNumber, ') @@ -2739,7 +2697,9 @@ abstract class _$AppDatabase extends GeneratedDatabase { late final $PlayerMatchTableTable playerMatchTable = $PlayerMatchTableTable( this, ); - late final $ScoreTableTable scoreTable = $ScoreTableTable(this); + late final $ScoreEntryTableTable scoreEntryTable = $ScoreEntryTableTable( + this, + ); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); late final GroupDao groupDao = GroupDao(this as AppDatabase); late final MatchDao matchDao = MatchDao(this as AppDatabase); @@ -2764,7 +2724,7 @@ abstract class _$AppDatabase extends GeneratedDatabase { playerGroupTable, teamTable, playerMatchTable, - scoreTable, + scoreEntryTable, ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ @@ -2815,14 +2775,14 @@ abstract class _$AppDatabase extends GeneratedDatabase { 'player_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('score_table', kind: UpdateKind.delete)], + result: [TableUpdate('score_entry_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( 'match_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('score_table', kind: UpdateKind.delete)], + result: [TableUpdate('score_entry_table', kind: UpdateKind.delete)], ), ]); } @@ -2894,19 +2854,24 @@ final class $$PlayerTableTableReferences ); } - static MultiTypedResultKey<$ScoreTableTable, List> - _scoreTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.scoreTable, - aliasName: $_aliasNameGenerator(db.playerTable.id, db.scoreTable.playerId), + static MultiTypedResultKey<$ScoreEntryTableTable, List> + _scoreEntryTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.scoreEntryTable, + aliasName: $_aliasNameGenerator( + db.playerTable.id, + db.scoreEntryTable.playerId, + ), ); - $$ScoreTableTableProcessedTableManager get scoreTableRefs { - final manager = $$ScoreTableTableTableManager( + $$ScoreEntryTableTableProcessedTableManager get scoreEntryTableRefs { + final manager = $$ScoreEntryTableTableTableManager( $_db, - $_db.scoreTable, + $_db.scoreEntryTable, ).filter((f) => f.playerId.id.sqlEquals($_itemColumn('id')!)); - final cache = $_typedResult.readTableOrNull(_scoreTableRefsTable($_db)); + final cache = $_typedResult.readTableOrNull( + _scoreEntryTableRefsTable($_db), + ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); @@ -2992,22 +2957,22 @@ class $$PlayerTableTableFilterComposer return f(composer); } - Expression scoreTableRefs( - Expression Function($$ScoreTableTableFilterComposer f) f, + Expression scoreEntryTableRefs( + Expression Function($$ScoreEntryTableTableFilterComposer f) f, ) { - final $$ScoreTableTableFilterComposer composer = $composerBuilder( + final $$ScoreEntryTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.scoreTable, + referencedTable: $db.scoreEntryTable, getReferencedColumn: (t) => t.playerId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$ScoreTableTableFilterComposer( + }) => $$ScoreEntryTableTableFilterComposer( $db: $db, - $table: $db.scoreTable, + $table: $db.scoreEntryTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3121,22 +3086,22 @@ class $$PlayerTableTableAnnotationComposer return f(composer); } - Expression scoreTableRefs( - Expression Function($$ScoreTableTableAnnotationComposer a) f, + Expression scoreEntryTableRefs( + Expression Function($$ScoreEntryTableTableAnnotationComposer a) f, ) { - final $$ScoreTableTableAnnotationComposer composer = $composerBuilder( + final $$ScoreEntryTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.scoreTable, + referencedTable: $db.scoreEntryTable, getReferencedColumn: (t) => t.playerId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$ScoreTableTableAnnotationComposer( + }) => $$ScoreEntryTableTableAnnotationComposer( $db: $db, - $table: $db.scoreTable, + $table: $db.scoreEntryTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3163,7 +3128,7 @@ class $$PlayerTableTableTableManager PrefetchHooks Function({ bool playerGroupTableRefs, bool playerMatchTableRefs, - bool scoreTableRefs, + bool scoreEntryTableRefs, }) > { $$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table) @@ -3217,14 +3182,14 @@ class $$PlayerTableTableTableManager ({ playerGroupTableRefs = false, playerMatchTableRefs = false, - scoreTableRefs = false, + scoreEntryTableRefs = false, }) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGroupTableRefs) db.playerGroupTable, if (playerMatchTableRefs) db.playerMatchTable, - if (scoreTableRefs) db.scoreTable, + if (scoreEntryTableRefs) db.scoreEntryTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -3271,21 +3236,21 @@ class $$PlayerTableTableTableManager ), typedResults: items, ), - if (scoreTableRefs) + if (scoreEntryTableRefs) await $_getPrefetchedData< PlayerTableData, $PlayerTableTable, - ScoreTableData + ScoreEntryTableData >( currentTable: table, referencedTable: $$PlayerTableTableReferences - ._scoreTableRefsTable(db), + ._scoreEntryTableRefsTable(db), managerFromTypedResult: (p0) => $$PlayerTableTableReferences( db, table, p0, - ).scoreTableRefs, + ).scoreEntryTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( (e) => e.playerId == item.id, @@ -3315,7 +3280,7 @@ typedef $$PlayerTableTableProcessedTableManager = PrefetchHooks Function({ bool playerGroupTableRefs, bool playerMatchTableRefs, - bool scoreTableRefs, + bool scoreEntryTableRefs, }) >; typedef $$GroupTableTableCreateCompanionBuilder = @@ -4051,7 +4016,7 @@ typedef $$MatchTableTableCreateCompanionBuilder = required String id, required String gameId, Value groupId, - Value name, + required String name, Value notes, required DateTime createdAt, Value endedAt, @@ -4062,7 +4027,7 @@ typedef $$MatchTableTableUpdateCompanionBuilder = Value id, Value gameId, Value groupId, - Value name, + Value name, Value notes, Value createdAt, Value endedAt, @@ -4132,19 +4097,24 @@ final class $$MatchTableTableReferences ); } - static MultiTypedResultKey<$ScoreTableTable, List> - _scoreTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.scoreTable, - aliasName: $_aliasNameGenerator(db.matchTable.id, db.scoreTable.matchId), + static MultiTypedResultKey<$ScoreEntryTableTable, List> + _scoreEntryTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.scoreEntryTable, + aliasName: $_aliasNameGenerator( + db.matchTable.id, + db.scoreEntryTable.matchId, + ), ); - $$ScoreTableTableProcessedTableManager get scoreTableRefs { - final manager = $$ScoreTableTableTableManager( + $$ScoreEntryTableTableProcessedTableManager get scoreEntryTableRefs { + final manager = $$ScoreEntryTableTableTableManager( $_db, - $_db.scoreTable, + $_db.scoreEntryTable, ).filter((f) => f.matchId.id.sqlEquals($_itemColumn('id')!)); - final cache = $_typedResult.readTableOrNull(_scoreTableRefsTable($_db)); + final cache = $_typedResult.readTableOrNull( + _scoreEntryTableRefsTable($_db), + ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); @@ -4256,22 +4226,22 @@ class $$MatchTableTableFilterComposer return f(composer); } - Expression scoreTableRefs( - Expression Function($$ScoreTableTableFilterComposer f) f, + Expression scoreEntryTableRefs( + Expression Function($$ScoreEntryTableTableFilterComposer f) f, ) { - final $$ScoreTableTableFilterComposer composer = $composerBuilder( + final $$ScoreEntryTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.scoreTable, + referencedTable: $db.scoreEntryTable, getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$ScoreTableTableFilterComposer( + }) => $$ScoreEntryTableTableFilterComposer( $db: $db, - $table: $db.scoreTable, + $table: $db.scoreEntryTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -4458,22 +4428,22 @@ class $$MatchTableTableAnnotationComposer return f(composer); } - Expression scoreTableRefs( - Expression Function($$ScoreTableTableAnnotationComposer a) f, + Expression scoreEntryTableRefs( + Expression Function($$ScoreEntryTableTableAnnotationComposer a) f, ) { - final $$ScoreTableTableAnnotationComposer composer = $composerBuilder( + final $$ScoreEntryTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.scoreTable, + referencedTable: $db.scoreEntryTable, getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$ScoreTableTableAnnotationComposer( + }) => $$ScoreEntryTableTableAnnotationComposer( $db: $db, - $table: $db.scoreTable, + $table: $db.scoreEntryTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -4501,7 +4471,7 @@ class $$MatchTableTableTableManager bool gameId, bool groupId, bool playerMatchTableRefs, - bool scoreTableRefs, + bool scoreEntryTableRefs, }) > { $$MatchTableTableTableManager(_$AppDatabase db, $MatchTableTable table) @@ -4520,7 +4490,7 @@ class $$MatchTableTableTableManager Value id = const Value.absent(), Value gameId = const Value.absent(), Value groupId = const Value.absent(), - Value name = const Value.absent(), + Value name = const Value.absent(), Value notes = const Value.absent(), Value createdAt = const Value.absent(), Value endedAt = const Value.absent(), @@ -4540,7 +4510,7 @@ class $$MatchTableTableTableManager required String id, required String gameId, Value groupId = const Value.absent(), - Value name = const Value.absent(), + required String name, Value notes = const Value.absent(), required DateTime createdAt, Value endedAt = const Value.absent(), @@ -4568,13 +4538,13 @@ class $$MatchTableTableTableManager gameId = false, groupId = false, playerMatchTableRefs = false, - scoreTableRefs = false, + scoreEntryTableRefs = false, }) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerMatchTableRefs) db.playerMatchTable, - if (scoreTableRefs) db.scoreTable, + if (scoreEntryTableRefs) db.scoreEntryTable, ], addJoins: < @@ -4646,21 +4616,21 @@ class $$MatchTableTableTableManager ), typedResults: items, ), - if (scoreTableRefs) + if (scoreEntryTableRefs) await $_getPrefetchedData< MatchTableData, $MatchTableTable, - ScoreTableData + ScoreEntryTableData >( currentTable: table, referencedTable: $$MatchTableTableReferences - ._scoreTableRefsTable(db), + ._scoreEntryTableRefsTable(db), managerFromTypedResult: (p0) => $$MatchTableTableReferences( db, table, p0, - ).scoreTableRefs, + ).scoreEntryTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( (e) => e.matchId == item.id, @@ -4691,7 +4661,7 @@ typedef $$MatchTableTableProcessedTableManager = bool gameId, bool groupId, bool playerMatchTableRefs, - bool scoreTableRefs, + bool scoreEntryTableRefs, }) >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = @@ -5334,7 +5304,6 @@ typedef $$PlayerMatchTableTableCreateCompanionBuilder = required String playerId, required String matchId, Value teamId, - required int score, Value rowid, }); typedef $$PlayerMatchTableTableUpdateCompanionBuilder = @@ -5342,7 +5311,6 @@ typedef $$PlayerMatchTableTableUpdateCompanionBuilder = Value playerId, Value matchId, Value teamId, - Value score, Value rowid, }); @@ -5426,11 +5394,6 @@ class $$PlayerMatchTableTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get score => $composableBuilder( - column: $table.score, - builder: (column) => ColumnFilters(column), - ); - $$PlayerTableTableFilterComposer get playerId { final $$PlayerTableTableFilterComposer composer = $composerBuilder( composer: this, @@ -5510,11 +5473,6 @@ class $$PlayerMatchTableTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get score => $composableBuilder( - column: $table.score, - builder: (column) => ColumnOrderings(column), - ); - $$PlayerTableTableOrderingComposer get playerId { final $$PlayerTableTableOrderingComposer composer = $composerBuilder( composer: this, @@ -5594,9 +5552,6 @@ class $$PlayerMatchTableTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get score => - $composableBuilder(column: $table.score, builder: (column) => column); - $$PlayerTableTableAnnotationComposer get playerId { final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( composer: this, @@ -5700,13 +5655,11 @@ class $$PlayerMatchTableTableTableManager Value playerId = const Value.absent(), Value matchId = const Value.absent(), Value teamId = const Value.absent(), - Value score = const Value.absent(), Value rowid = const Value.absent(), }) => PlayerMatchTableCompanion( playerId: playerId, matchId: matchId, teamId: teamId, - score: score, rowid: rowid, ), createCompanionCallback: @@ -5714,13 +5667,11 @@ class $$PlayerMatchTableTableTableManager required String playerId, required String matchId, Value teamId = const Value.absent(), - required int score, Value rowid = const Value.absent(), }) => PlayerMatchTableCompanion.insert( playerId: playerId, matchId: matchId, teamId: teamId, - score: score, rowid: rowid, ), withReferenceMapper: (p0) => p0 @@ -5823,8 +5774,8 @@ typedef $$PlayerMatchTableTableProcessedTableManager = PlayerMatchTableData, PrefetchHooks Function({bool playerId, bool matchId, bool teamId}) >; -typedef $$ScoreTableTableCreateCompanionBuilder = - ScoreTableCompanion Function({ +typedef $$ScoreEntryTableTableCreateCompanionBuilder = + ScoreEntryTableCompanion Function({ required String playerId, required String matchId, required int roundNumber, @@ -5832,8 +5783,8 @@ typedef $$ScoreTableTableCreateCompanionBuilder = required int change, Value rowid, }); -typedef $$ScoreTableTableUpdateCompanionBuilder = - ScoreTableCompanion Function({ +typedef $$ScoreEntryTableTableUpdateCompanionBuilder = + ScoreEntryTableCompanion Function({ Value playerId, Value matchId, Value roundNumber, @@ -5842,13 +5793,22 @@ typedef $$ScoreTableTableUpdateCompanionBuilder = Value rowid, }); -final class $$ScoreTableTableReferences - extends BaseReferences<_$AppDatabase, $ScoreTableTable, ScoreTableData> { - $$ScoreTableTableReferences(super.$_db, super.$_table, super.$_typedResult); +final class $$ScoreEntryTableTableReferences + extends + BaseReferences< + _$AppDatabase, + $ScoreEntryTableTable, + ScoreEntryTableData + > { + $$ScoreEntryTableTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); static $PlayerTableTable _playerIdTable(_$AppDatabase db) => db.playerTable.createAlias( - $_aliasNameGenerator(db.scoreTable.playerId, db.playerTable.id), + $_aliasNameGenerator(db.scoreEntryTable.playerId, db.playerTable.id), ); $$PlayerTableTableProcessedTableManager get playerId { @@ -5867,7 +5827,7 @@ final class $$ScoreTableTableReferences static $MatchTableTable _matchIdTable(_$AppDatabase db) => db.matchTable.createAlias( - $_aliasNameGenerator(db.scoreTable.matchId, db.matchTable.id), + $_aliasNameGenerator(db.scoreEntryTable.matchId, db.matchTable.id), ); $$MatchTableTableProcessedTableManager get matchId { @@ -5885,9 +5845,9 @@ final class $$ScoreTableTableReferences } } -class $$ScoreTableTableFilterComposer - extends Composer<_$AppDatabase, $ScoreTableTable> { - $$ScoreTableTableFilterComposer({ +class $$ScoreEntryTableTableFilterComposer + extends Composer<_$AppDatabase, $ScoreEntryTableTable> { + $$ScoreEntryTableTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -5956,9 +5916,9 @@ class $$ScoreTableTableFilterComposer } } -class $$ScoreTableTableOrderingComposer - extends Composer<_$AppDatabase, $ScoreTableTable> { - $$ScoreTableTableOrderingComposer({ +class $$ScoreEntryTableTableOrderingComposer + extends Composer<_$AppDatabase, $ScoreEntryTableTable> { + $$ScoreEntryTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -6027,9 +5987,9 @@ class $$ScoreTableTableOrderingComposer } } -class $$ScoreTableTableAnnotationComposer - extends Composer<_$AppDatabase, $ScoreTableTable> { - $$ScoreTableTableAnnotationComposer({ +class $$ScoreEntryTableTableAnnotationComposer + extends Composer<_$AppDatabase, $ScoreEntryTableTable> { + $$ScoreEntryTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -6094,32 +6054,34 @@ class $$ScoreTableTableAnnotationComposer } } -class $$ScoreTableTableTableManager +class $$ScoreEntryTableTableTableManager extends RootTableManager< _$AppDatabase, - $ScoreTableTable, - ScoreTableData, - $$ScoreTableTableFilterComposer, - $$ScoreTableTableOrderingComposer, - $$ScoreTableTableAnnotationComposer, - $$ScoreTableTableCreateCompanionBuilder, - $$ScoreTableTableUpdateCompanionBuilder, - (ScoreTableData, $$ScoreTableTableReferences), - ScoreTableData, + $ScoreEntryTableTable, + ScoreEntryTableData, + $$ScoreEntryTableTableFilterComposer, + $$ScoreEntryTableTableOrderingComposer, + $$ScoreEntryTableTableAnnotationComposer, + $$ScoreEntryTableTableCreateCompanionBuilder, + $$ScoreEntryTableTableUpdateCompanionBuilder, + (ScoreEntryTableData, $$ScoreEntryTableTableReferences), + ScoreEntryTableData, PrefetchHooks Function({bool playerId, bool matchId}) > { - $$ScoreTableTableTableManager(_$AppDatabase db, $ScoreTableTable table) - : super( + $$ScoreEntryTableTableTableManager( + _$AppDatabase db, + $ScoreEntryTableTable table, + ) : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$ScoreTableTableFilterComposer($db: db, $table: table), + $$ScoreEntryTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$ScoreTableTableOrderingComposer($db: db, $table: table), + $$ScoreEntryTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$ScoreTableTableAnnotationComposer($db: db, $table: table), + $$ScoreEntryTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value playerId = const Value.absent(), @@ -6128,7 +6090,7 @@ class $$ScoreTableTableTableManager Value score = const Value.absent(), Value change = const Value.absent(), Value rowid = const Value.absent(), - }) => ScoreTableCompanion( + }) => ScoreEntryTableCompanion( playerId: playerId, matchId: matchId, roundNumber: roundNumber, @@ -6144,7 +6106,7 @@ class $$ScoreTableTableTableManager required int score, required int change, Value rowid = const Value.absent(), - }) => ScoreTableCompanion.insert( + }) => ScoreEntryTableCompanion.insert( playerId: playerId, matchId: matchId, roundNumber: roundNumber, @@ -6156,7 +6118,7 @@ class $$ScoreTableTableTableManager .map( (e) => ( e.readTable(table), - $$ScoreTableTableReferences(db, table, e), + $$ScoreEntryTableTableReferences(db, table, e), ), ) .toList(), @@ -6185,11 +6147,13 @@ class $$ScoreTableTableTableManager state.withJoin( currentTable: table, currentColumn: table.playerId, - referencedTable: $$ScoreTableTableReferences - ._playerIdTable(db), - referencedColumn: $$ScoreTableTableReferences - ._playerIdTable(db) - .id, + referencedTable: + $$ScoreEntryTableTableReferences + ._playerIdTable(db), + referencedColumn: + $$ScoreEntryTableTableReferences + ._playerIdTable(db) + .id, ) as T; } @@ -6198,11 +6162,13 @@ class $$ScoreTableTableTableManager state.withJoin( currentTable: table, currentColumn: table.matchId, - referencedTable: $$ScoreTableTableReferences - ._matchIdTable(db), - referencedColumn: $$ScoreTableTableReferences - ._matchIdTable(db) - .id, + referencedTable: + $$ScoreEntryTableTableReferences + ._matchIdTable(db), + referencedColumn: + $$ScoreEntryTableTableReferences + ._matchIdTable(db) + .id, ) as T; } @@ -6218,18 +6184,18 @@ class $$ScoreTableTableTableManager ); } -typedef $$ScoreTableTableProcessedTableManager = +typedef $$ScoreEntryTableTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $ScoreTableTable, - ScoreTableData, - $$ScoreTableTableFilterComposer, - $$ScoreTableTableOrderingComposer, - $$ScoreTableTableAnnotationComposer, - $$ScoreTableTableCreateCompanionBuilder, - $$ScoreTableTableUpdateCompanionBuilder, - (ScoreTableData, $$ScoreTableTableReferences), - ScoreTableData, + $ScoreEntryTableTable, + ScoreEntryTableData, + $$ScoreEntryTableTableFilterComposer, + $$ScoreEntryTableTableOrderingComposer, + $$ScoreEntryTableTableAnnotationComposer, + $$ScoreEntryTableTableCreateCompanionBuilder, + $$ScoreEntryTableTableUpdateCompanionBuilder, + (ScoreEntryTableData, $$ScoreEntryTableTableReferences), + ScoreEntryTableData, PrefetchHooks Function({bool playerId, bool matchId}) >; @@ -6250,6 +6216,6 @@ class $AppDatabaseManager { $$TeamTableTableTableManager(_db, _db.teamTable); $$PlayerMatchTableTableTableManager get playerMatchTable => $$PlayerMatchTableTableTableManager(_db, _db.playerMatchTable); - $$ScoreTableTableTableManager get scoreTable => - $$ScoreTableTableTableManager(_db, _db.scoreTable); + $$ScoreEntryTableTableTableManager get scoreEntryTable => + $$ScoreEntryTableTableTableManager(_db, _db.scoreEntryTable); } diff --git a/lib/data/db/tables/player_match_table.dart b/lib/data/db/tables/player_match_table.dart index 3ff55ea..30412ab 100644 --- a/lib/data/db/tables/player_match_table.dart +++ b/lib/data/db/tables/player_match_table.dart @@ -8,9 +8,7 @@ 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()(); - IntColumn get score => integer()(); + TextColumn get teamId => text().references(TeamTable, #id).nullable()(); @override Set> get primaryKey => {playerId, matchId}; diff --git a/lib/data/db/tables/score_table.dart b/lib/data/db/tables/score_entry_table.dart similarity index 93% rename from lib/data/db/tables/score_table.dart rename to lib/data/db/tables/score_entry_table.dart index a7bf122..7852125 100644 --- a/lib/data/db/tables/score_table.dart +++ b/lib/data/db/tables/score_entry_table.dart @@ -2,7 +2,7 @@ import 'package:drift/drift.dart'; import 'package:tallee/data/db/tables/match_table.dart'; import 'package:tallee/data/db/tables/player_table.dart'; -class ScoreTable extends Table { +class ScoreEntryTable extends Table { TextColumn get playerId => text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); TextColumn get matchId => @@ -13,4 +13,4 @@ class ScoreTable extends Table { @override Set> get primaryKey => {playerId, matchId, roundNumber}; -} \ No newline at end of file +} diff --git a/lib/data/dto/match.dart b/lib/data/dto/match.dart deleted file mode 100644 index 3976d36..0000000 --- a/lib/data/dto/match.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:tallee/core/enums.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/player.dart'; -import 'package:uuid/uuid.dart'; - -class Match { - final String id; - final DateTime createdAt; - final DateTime? endedAt; - final String name; - final Game game; - final Group? group; - final List players; - final String notes; - Player? winner; - - Match({ - String? id, - DateTime? createdAt, - this.endedAt, - required this.name, - required this.game, - this.group, - this.players = const [], - String? notes, - this.winner, - }) : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? clock.now(), - notes = notes ?? ''; - - @override - String toString() { - return 'Match{id: $id, name: $name, game: $game, group: $group, players: $players, notes: $notes, endedAt: $endedAt}'; - } - - /// Creates a Match instance from a JSON object (ID references format). - /// Related objects are reconstructed from IDs by the DataTransferService. - Match.fromJson(Map json) - : id = json['id'], - createdAt = DateTime.parse(json['createdAt']), - endedAt = json['endedAt'] != null ? DateTime.parse(json['endedAt']) : null, - name = json['name'], - game = Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), // Populated during import via DataTransferService - group = null, // Populated during import via DataTransferService - players = [], // Populated during import via DataTransferService - notes = json['notes'] ?? ''; - - /// Converts the Match instance to a JSON object using normalized format (ID references only). - Map toJson() => { - 'id': id, - 'createdAt': createdAt.toIso8601String(), - 'endedAt': endedAt?.toIso8601String(), - 'name': name, - 'gameId': game.id, - 'groupId': group?.id, - 'playerIds': players.map((player) => player.id).toList(), - 'notes': notes, - }; -} diff --git a/lib/data/dto/game.dart b/lib/data/models/game.dart similarity index 100% rename from lib/data/dto/game.dart rename to lib/data/models/game.dart diff --git a/lib/data/dto/group.dart b/lib/data/models/group.dart similarity index 96% rename from lib/data/dto/group.dart rename to lib/data/models/group.dart index 7676b1e..aa3961c 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/models/group.dart @@ -1,5 +1,5 @@ import 'package:clock/clock.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/player.dart'; import 'package:uuid/uuid.dart'; class Group { diff --git a/lib/data/models/match.dart b/lib/data/models/match.dart new file mode 100644 index 0000000..a6d91c7 --- /dev/null +++ b/lib/data/models/match.dart @@ -0,0 +1,77 @@ +import 'package:clock/clock.dart'; +import 'package:tallee/core/enums.dart'; +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:uuid/uuid.dart'; + +class Match { + final String id; + final DateTime createdAt; + final DateTime? endedAt; + final String name; + final Game game; + final Group? group; + final List players; + final String notes; + Map> scores; + Player? winner; + + Match({ + String? id, + DateTime? createdAt, + this.endedAt, + required this.name, + required this.game, + this.group, + this.players = const [], + this.notes = '', + Map>? scores, + this.winner, + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? clock.now(), + scores = scores ?? {for (var player in players) player.id: []}; + + @override + String toString() { + return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, winner: $winner}'; + } + + /// Creates a Match instance from a JSON object (ID references format). + /// Related objects are reconstructed from IDs by the DataTransferService. + Match.fromJson(Map json) + : id = json['id'], + createdAt = DateTime.parse(json['createdAt']), + endedAt = json['endedAt'] != null + ? DateTime.parse(json['endedAt']) + : null, + name = json['name'], + game = Game( + name: '', + ruleset: Ruleset.singleWinner, + description: '', + color: GameColor.blue, + icon: '', + ), // Populated during import via DataTransferService + group = null, // Populated during import via DataTransferService + players = [], // Populated during import via DataTransferService + scores = json['scores'], + notes = json['notes'] ?? ''; + + /// Converts the Match instance to a JSON object using normalized format (ID references only). + Map toJson() => { + 'id': id, + 'createdAt': createdAt.toIso8601String(), + 'endedAt': endedAt?.toIso8601String(), + 'name': name, + 'gameId': game.id, + 'groupId': group?.id, + 'playerIds': players.map((player) => player.id).toList(), + 'scores': scores.map( + (playerId, scoreList) => + MapEntry(playerId, scoreList.map((score) => score.toJson()).toList()), + ), + 'notes': notes, + }; +} diff --git a/lib/data/dto/player.dart b/lib/data/models/player.dart similarity index 100% rename from lib/data/dto/player.dart rename to lib/data/models/player.dart diff --git a/lib/data/models/score_entry.dart b/lib/data/models/score_entry.dart new file mode 100644 index 0000000..0f8a8c3 --- /dev/null +++ b/lib/data/models/score_entry.dart @@ -0,0 +1,22 @@ +class ScoreEntry { + int roundNumber = 0; + final int score; + final int change; + + ScoreEntry({ + required this.roundNumber, + required this.score, + required this.change, + }); + + ScoreEntry.fromJson(Map json) + : roundNumber = json['roundNumber'], + score = json['score'], + change = json['change']; + + Map toJson() => { + 'roundNumber': roundNumber, + 'score': score, + 'change': change, + }; +} diff --git a/lib/data/dto/team.dart b/lib/data/models/team.dart similarity index 95% rename from lib/data/dto/team.dart rename to lib/data/models/team.dart index 56036b2..8c16280 100644 --- a/lib/data/dto/team.dart +++ b/lib/data/models/team.dart @@ -1,5 +1,5 @@ import 'package:clock/clock.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/player.dart'; import 'package:uuid/uuid.dart'; class Team { @@ -37,4 +37,3 @@ class Team { 'memberIds': members.map((member) => member.id).toList(), }; } - diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 8829343..d5ac6a4 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -4,8 +4,8 @@ import 'package:tallee/core/constants.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart'; import 'package:tallee/presentation/widgets/player_selection.dart'; diff --git a/lib/presentation/views/main_menu/group_view/group_detail_view.dart b/lib/presentation/views/main_menu/group_view/group_detail_view.dart index c9ffa25..1ef89ef 100644 --- a/lib/presentation/views/main_menu/group_view/group_detail_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_detail_view.dart @@ -4,9 +4,9 @@ import 'package:provider/provider.dart'; import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.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/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:tallee/presentation/widgets/app_skeleton.dart'; diff --git a/lib/presentation/views/main_menu/group_view/group_view.dart b/lib/presentation/views/main_menu/group_view/group_view.dart index 90b682a..b091541 100644 --- a/lib/presentation/views/main_menu/group_view/group_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_view.dart @@ -4,8 +4,8 @@ import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/constants.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:tallee/presentation/views/main_menu/group_view/group_detail_view.dart'; diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index a7f5cfa..651b34d 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -4,10 +4,10 @@ import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/constants.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.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/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:tallee/presentation/widgets/app_skeleton.dart'; @@ -42,7 +42,13 @@ class _HomeViewState extends State { 2, Match( name: 'Skeleton Match', - game: Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), + game: Game( + name: '', + ruleset: Ruleset.singleWinner, + description: '', + color: GameColor.blue, + icon: '', + ), group: Group( name: 'Skeleton Group', description: '', @@ -104,7 +110,9 @@ class _HomeViewState extends State { if (recentMatches.isNotEmpty) for (Match match in recentMatches) Padding( - padding: const EdgeInsets.symmetric(vertical: 6.0), + padding: const EdgeInsets.symmetric( + vertical: 6.0, + ), child: MatchTile( compact: true, width: constraints.maxWidth * 0.9, @@ -113,7 +121,8 @@ class _HomeViewState extends State { await Navigator.of(context).push( adaptivePageRoute( fullscreenDialog: true, - builder: (context) => MatchResultView(match: match), + builder: (context) => + MatchResultView(match: match), ), ); await updatedWinnerInRecentMatches(match.id); @@ -121,7 +130,10 @@ class _HomeViewState extends State { ), ) else - Center(heightFactor: 5, child: Text(loc.no_recent_matches_available)), + Center( + heightFactor: 5, + child: Text(loc.no_recent_matches_available), + ), ], ), ), @@ -137,22 +149,40 @@ class _HomeViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - QuickCreateButton(text: 'Category 1', onPressed: () {}), - QuickCreateButton(text: 'Category 2', onPressed: () {}), + QuickCreateButton( + text: 'Category 1', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 2', + onPressed: () {}, + ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - QuickCreateButton(text: 'Category 3', onPressed: () {}), - QuickCreateButton(text: 'Category 4', onPressed: () {}), + QuickCreateButton( + text: 'Category 3', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 4', + onPressed: () {}, + ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - QuickCreateButton(text: 'Category 5', onPressed: () {}), - QuickCreateButton(text: 'Category 6', onPressed: () {}), + QuickCreateButton( + text: 'Category 5', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 6', + onPressed: () {}, + ), ], ), ], @@ -181,9 +211,11 @@ class _HomeViewState extends State { matchCount = results[0] as int; groupCount = results[1] as int; loadedRecentMatches = results[2] as List; - recentMatches = (loadedRecentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .take(2) - .toList(); + recentMatches = + (loadedRecentMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); if (mounted) { setState(() { isLoading = false; @@ -195,7 +227,7 @@ class _HomeViewState extends State { /// Updates the winner information for a specific match in the recent matches list. Future updatedWinnerInRecentMatches(String matchId) async { final db = Provider.of(context, listen: false); - final winner = await db.matchDao.getWinner(matchId: matchId); + final winner = await db.scoreDao.getWinner(matchId: matchId); final matchIndex = recentMatches.indexWhere((match) => match.id == matchId); if (matchIndex != -1) { setState(() { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 9c60b16..c7471d8 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:tallee/core/custom_theme.dart'; -import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/models/group.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:tallee/presentation/widgets/tiles/group_tile.dart'; diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 8138957..950b3a8 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -5,10 +5,10 @@ import 'package:tallee/core/constants.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.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/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; diff --git a/lib/presentation/views/main_menu/match_view/match_detail_view.dart b/lib/presentation/views/main_menu/match_view/match_detail_view.dart index 1deba18..fc53aa8 100644 --- a/lib/presentation/views/main_menu/match_view/match_detail_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_detail_view.dart @@ -5,7 +5,7 @@ import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/models/match.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 8a46f13..6dfe832 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/match.dart'; +import 'package:tallee/data/models/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart'; @@ -139,11 +139,11 @@ class _MatchResultViewState extends State { /// based on the current selection. Future _handleWinnerSaving() async { if (_selectedPlayer == null) { - await db.matchDao.removeWinner(matchId: widget.match.id); + await db.scoreDao.removeWinner(matchId: widget.match.id); } else { - await db.matchDao.setWinner( + await db.scoreDao.setWinner( matchId: widget.match.id, - winnerId: _selectedPlayer!.id, + playerId: _selectedPlayer!.id, ); } widget.onWinnerChanged?.call(); diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index a090b46..1a202c4 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -6,10 +6,10 @@ import 'package:tallee/core/constants.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.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/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; import 'package:tallee/presentation/views/main_menu/match_view/match_detail_view.dart'; diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 5b3ff22..3a55115 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/constants.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/match.dart'; +import 'package:tallee/data/models/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/app_skeleton.dart'; import 'package:tallee/presentation/widgets/tiles/statistics_tile.dart'; @@ -167,7 +167,8 @@ class _StatisticsViewState extends State { final playerId = winCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: loc.not_available, description: ''), + orElse: () => + Player(id: playerId, name: loc.not_available, description: ''), ); winCounts[i] = (player.name, winCounts[i].$2); } @@ -208,11 +209,11 @@ class _StatisticsViewState extends State { // -1 means player not found in matchCounts if (index != -1) { final current = matchCounts[index].$2; - matchCounts[index] = (playerId, current + 1); - } else { - matchCounts.add((playerId, 1)); - } + matchCounts[index] = (playerId, current + 1); + } else { + matchCounts.add((playerId, 1)); } + } } // Adding all players with zero matches @@ -229,7 +230,8 @@ class _StatisticsViewState extends State { final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: loc.not_available, description: ''), + orElse: () => + Player(id: playerId, name: loc.not_available, description: ''), ); matchCounts[i] = (player.name, matchCounts[i].$2); } diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index b51cadc..6d8769d 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -3,7 +3,7 @@ import 'package:provider/provider.dart'; import 'package:tallee/core/constants.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/app_skeleton.dart'; import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart'; diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index d662918..b62f3ce 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:tallee/core/custom_theme.dart'; -import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/models/group.dart'; import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; class GroupTile extends StatefulWidget { diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 3c36587..39f9cdf 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; -import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/models/match.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 526a459..6c7455d 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -8,11 +8,11 @@ import 'package:json_schema/json_schema.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.dart'; -import 'package:tallee/data/dto/team.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/player.dart'; +import 'package:tallee/data/models/team.dart'; class DataTransferService { /// Deletes all data from the database. @@ -40,33 +40,53 @@ class DataTransferService { 'players': players.map((p) => p.toJson()).toList(), 'games': games.map((g) => g.toJson()).toList(), 'groups': groups - .map((g) => { - 'id': g.id, - 'name': g.name, - 'description': g.description, - 'createdAt': g.createdAt.toIso8601String(), - 'memberIds': (g.members).map((m) => m.id).toList(), - }) + .map( + (g) => { + 'id': g.id, + 'name': g.name, + 'description': g.description, + 'createdAt': g.createdAt.toIso8601String(), + 'memberIds': (g.members).map((m) => m.id).toList(), + }, + ) .toList(), 'teams': teams - .map((t) => { - 'id': t.id, - 'name': t.name, - 'createdAt': t.createdAt.toIso8601String(), - 'memberIds': (t.members).map((m) => m.id).toList(), - }) + .map( + (t) => { + 'id': t.id, + 'name': t.name, + 'createdAt': t.createdAt.toIso8601String(), + 'memberIds': (t.members).map((m) => m.id).toList(), + }, + ) .toList(), 'matches': matches - .map((m) => { - 'id': m.id, - 'name': m.name, - 'createdAt': m.createdAt.toIso8601String(), - 'endedAt': m.endedAt?.toIso8601String(), - 'gameId': m.game.id, - 'groupId': m.group?.id, - 'playerIds': m.players.map((p) => p.id).toList(), - 'notes': m.notes, - }) + .map( + (m) => { + 'id': m.id, + 'name': m.name, + 'createdAt': m.createdAt.toIso8601String(), + 'endedAt': m.endedAt?.toIso8601String(), + 'gameId': m.game.id, + 'groupId': m.group?.id, + 'playerIds': m.players.map((p) => p.id).toList(), + 'scores': m.scores.map( + (playerId, scores) => MapEntry( + playerId, + scores + .map( + (s) => { + 'roundNumber': s.roundNumber, + 'score': s.score, + 'change': s.change, + }, + ) + .toList(), + ), + ), + 'notes': m.notes, + }, + ) .toList(), }; @@ -79,9 +99,9 @@ class DataTransferService { /// [jsonString] The JSON string to be exported. /// [fileName] The desired name for the exported file (without extension). static Future exportData( - String jsonString, - String fileName - ) async { + String jsonString, + String fileName, + ) async { try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); final path = await FilePicker.platform.saveFile( @@ -94,7 +114,6 @@ class DataTransferService { } else { return ExportResult.success; } - } catch (e, stack) { print('[exportData] $e'); print(stack); @@ -119,110 +138,12 @@ class DataTransferService { final jsonString = await _readFileContent(path.files.single); if (jsonString == null) return ImportResult.fileReadError; - final isValid = await _validateJsonSchema(jsonString); + final isValid = await validateJsonSchema(jsonString); if (!isValid) return ImportResult.invalidSchema; - final Map decoded = json.decode(jsonString) as Map; + final decoded = json.decode(jsonString) as Map; - final List playersJson = (decoded['players'] as List?) ?? []; - final List gamesJson = (decoded['games'] as List?) ?? []; - final List groupsJson = (decoded['groups'] as List?) ?? []; - final List teamsJson = (decoded['teams'] as List?) ?? []; - final List matchesJson = (decoded['matches'] as List?) ?? []; - - // Import Players - final List importedPlayers = playersJson - .map((p) => Player.fromJson(p as Map)) - .toList(); - - final Map playerById = { - for (final p in importedPlayers) p.id: p, - }; - - // Import Games - final List importedGames = gamesJson - .map((g) => Game.fromJson(g as Map)) - .toList(); - - final Map gameById = { - for (final g in importedGames) g.id: g, - }; - - // Import Groups - final List importedGroups = groupsJson.map((g) { - final map = g as Map; - final memberIds = (map['memberIds'] as List? ?? []).cast(); - - final members = memberIds - .map((id) => playerById[id]) - .whereType() - .toList(); - - return Group( - id: map['id'] as String, - name: map['name'] as String, - description: map['description'] as String, - members: members, - createdAt: DateTime.parse(map['createdAt'] as String), - ); - }).toList(); - - final Map groupById = { - for (final g in importedGroups) g.id: g, - }; - - // Import Teams - final List importedTeams = teamsJson.map((t) { - final map = t as Map; - final memberIds = (map['memberIds'] as List? ?? []).cast(); - - final members = memberIds - .map((id) => playerById[id]) - .whereType() - .toList(); - - return Team( - id: map['id'] as String, - name: map['name'] as String, - members: members, - createdAt: DateTime.parse(map['createdAt'] as String), - ); - }).toList(); - - // Import Matches - final List importedMatches = matchesJson.map((m) { - final map = m as Map; - - final String gameId = map['gameId'] as String; - final String? groupId = map['groupId'] as String?; - final List playerIds = (map['playerIds'] as List? ?? []).cast(); - final DateTime? endedAt = map['endedAt'] != null ? DateTime.parse(map['endedAt'] as String) : null; - - final game = gameById[gameId]; - final group = (groupId == null) ? null : groupById[groupId]; - final players = playerIds - .map((id) => playerById[id]) - .whereType() - .toList(); - - return Match( - id: map['id'] as String, - name: map['name'] as String, - game: game ?? Game(name: 'Unknown', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), - group: group, - players: players, - createdAt: DateTime.parse(map['createdAt'] as String), - endedAt: endedAt, - notes: map['notes'] as String? ?? '', - ); - }).toList(); - - // Import all data into the database - 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.addMatchAsList(matches: importedMatches); + await importDataToDatabase(db, decoded); return ImportResult.success; } on FormatException catch (e, stack) { @@ -238,6 +159,160 @@ class DataTransferService { } } + /// Imports parsed JSON data into the database. + @visibleForTesting + static Future importDataToDatabase( + AppDatabase db, + Map decoded, + ) async { + final importedPlayers = parsePlayersFromJson(decoded); + final playerById = {for (final p in importedPlayers) p.id: p}; + + final importedGames = parseGamesFromJson(decoded); + final gameById = {for (final g in importedGames) g.id: g}; + + final importedGroups = parseGroupsFromJson(decoded, playerById); + final groupById = {for (final g in importedGroups) g.id: g}; + + final importedTeams = parseTeamsFromJson(decoded, playerById); + + final importedMatches = parseMatchesFromJson( + decoded, + gameById, + groupById, + playerById, + ); + + 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.addMatchAsList(matches: importedMatches); + } + + /// Parses players from JSON data. + @visibleForTesting + static List parsePlayersFromJson(Map decoded) { + final playersJson = (decoded['players'] as List?) ?? []; + return playersJson + .map((p) => Player.fromJson(p as Map)) + .toList(); + } + + /// Parses games from JSON data. + @visibleForTesting + static List parseGamesFromJson(Map decoded) { + final gamesJson = (decoded['games'] as List?) ?? []; + return gamesJson + .map((g) => Game.fromJson(g as Map)) + .toList(); + } + + /// Parses groups from JSON data. + @visibleForTesting + static List parseGroupsFromJson( + Map decoded, + Map playerById, + ) { + final groupsJson = (decoded['groups'] as List?) ?? []; + return groupsJson.map((g) { + final map = g as Map; + final memberIds = (map['memberIds'] as List? ?? []) + .cast(); + + final members = memberIds + .map((id) => playerById[id]) + .whereType() + .toList(); + + return Group( + id: map['id'] as String, + name: map['name'] as String, + description: map['description'] as String, + members: members, + createdAt: DateTime.parse(map['createdAt'] as String), + ); + }).toList(); + } + + /// Parses teams from JSON data. + @visibleForTesting + static List parseTeamsFromJson( + Map decoded, + Map playerById, + ) { + final teamsJson = (decoded['teams'] as List?) ?? []; + return teamsJson.map((t) { + final map = t as Map; + final memberIds = (map['memberIds'] as List? ?? []) + .cast(); + + final members = memberIds + .map((id) => playerById[id]) + .whereType() + .toList(); + + return Team( + id: map['id'] as String, + name: map['name'] as String, + members: members, + createdAt: DateTime.parse(map['createdAt'] as String), + ); + }).toList(); + } + + /// Parses matches from JSON data. + @visibleForTesting + static List parseMatchesFromJson( + Map decoded, + Map gameById, + Map groupById, + Map playerById, + ) { + final matchesJson = (decoded['matches'] as List?) ?? []; + return matchesJson.map((m) { + final map = m as Map; + + final gameId = map['gameId'] as String; + final groupId = map['groupId'] as String?; + final playerIds = (map['playerIds'] as List? ?? []) + .cast(); + final endedAt = map['endedAt'] != null + ? DateTime.parse(map['endedAt'] as String) + : null; + + final game = gameById[gameId] ?? createUnknownGame(); + final group = groupId != null ? groupById[groupId] : null; + final players = playerIds + .map((id) => playerById[id]) + .whereType() + .toList(); + + return Match( + id: map['id'] as String, + name: map['name'] as String, + game: game, + group: group, + players: players, + createdAt: DateTime.parse(map['createdAt'] as String), + endedAt: endedAt, + notes: map['notes'] as String? ?? '', + ); + }).toList(); + } + + /// Creates a fallback game when the referenced game is not found. + @visibleForTesting + static Game createUnknownGame() { + return Game( + name: 'Unknown', + ruleset: Ruleset.singleWinner, + description: '', + color: GameColor.blue, + icon: '', + ); + } + /// Helper method to read file content from either bytes or path static Future _readFileContent(PlatformFile file) async { if (file.bytes != null) return utf8.decode(file.bytes!); @@ -246,7 +321,8 @@ class DataTransferService { } /// Validates the given JSON string against the predefined schema. - static Future _validateJsonSchema(String jsonString) async { + @visibleForTesting + static Future validateJsonSchema(String jsonString) async { final String schemaString; schemaString = await rootBundle.loadString('assets/schema.json'); @@ -266,4 +342,4 @@ class DataTransferService { return false; } } -} \ No newline at end of file +} diff --git a/test/db_tests/aggregates/group_test.dart b/test/db_tests/aggregates/group_test.dart index a671232..5e713c4 100644 --- a/test/db_tests/aggregates/group_test.dart +++ b/test/db_tests/aggregates/group_test.dart @@ -3,8 +3,8 @@ import 'package:drift/drift.dart' hide isNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/player.dart'; void main() { late AppDatabase database; @@ -62,7 +62,6 @@ void main() { await database.close(); }); group('Group Tests', () { - // Verifies that a single group can be added and retrieved with all fields and members intact. test('Adding and fetching a single group works correctly', () async { await database.groupDao.addGroup(group: testGroup1); @@ -277,20 +276,20 @@ void main() { }); // 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); - }); + 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); + }, + ); // Verifies that deleteAllGroups removes all groups from the database. test('deleteAllGroups removes all groups', () async { - await database.groupDao.addGroupsAsList( - groups: [testGroup1, testGroup2], - ); + await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); final countBefore = await database.groupDao.getGroupCount(); expect(countBefore, 2); diff --git a/test/db_tests/aggregates/match_test.dart b/test/db_tests/aggregates/match_test.dart index f5fbeb6..e2cc72f 100644 --- a/test/db_tests/aggregates/match_test.dart +++ b/test/db_tests/aggregates/match_test.dart @@ -4,10 +4,10 @@ import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.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/player.dart'; void main() { late AppDatabase database; @@ -296,9 +296,9 @@ void main() { test('Setting a winner works correctly', () async { await database.matchDao.addMatch(match: testMatch1); - await database.matchDao.setWinner( + await database.scoreDao.setWinner( matchId: testMatch1.id, - winnerId: testPlayer5.id, + playerId: testPlayer5.id, ); final fetchedMatch = await database.matchDao.getMatchById( @@ -360,7 +360,6 @@ void main() { expect(matches, isEmpty); await database.matchDao.addMatch(match: testMatch1); - print(await database.matchDao.getAllMatches()); matches = await database.matchDao.getGroupMatches(groupId: testGroup1.id); diff --git a/test/db_tests/aggregates/team_test.dart b/test/db_tests/aggregates/team_test.dart index 17ceff9..327bc8f 100644 --- a/test/db_tests/aggregates/team_test.dart +++ b/test/db_tests/aggregates/team_test.dart @@ -2,12 +2,12 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.dart'; -import 'package:tallee/data/dto/team.dart'; import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/models/game.dart'; +import 'package:tallee/data/models/match.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/team.dart'; void main() { late AppDatabase database; @@ -37,20 +37,23 @@ void main() { testPlayer2 = Player(name: 'Bob', description: ''); testPlayer3 = Player(name: 'Charlie', description: ''); testPlayer4 = Player(name: 'Diana', description: ''); - testTeam1 = Team( - name: 'Team Alpha', - members: [testPlayer1, testPlayer2], + 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', + color: GameColor.blue, + icon: '', ); - testTeam2 = Team( - name: 'Team Beta', - members: [testPlayer3, testPlayer4], + testGame2 = Game( + name: 'Game 2', + ruleset: Ruleset.highestScore, + description: 'Test game 2', + color: GameColor.red, + icon: '', ); - testTeam3 = Team( - name: 'Team Gamma', - members: [testPlayer1, testPlayer3], - ); - testGame1 = Game(name: 'Game 1', ruleset: Ruleset.singleWinner, description: 'Test game 1', color: GameColor.blue, icon: ''); - testGame2 = Game(name: 'Game 2', ruleset: Ruleset.highestScore, description: 'Test game 2', color: GameColor.red, icon: ''); }); await database.playerDao.addPlayersAsList( @@ -65,7 +68,6 @@ 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); @@ -285,10 +287,7 @@ void main() { test('Updating team name to empty string works', () async { await database.teamDao.addTeam(team: testTeam1); - await database.teamDao.updateTeamName( - teamId: testTeam1.id, - newName: '', - ); + await database.teamDao.updateTeamName(teamId: testTeam1.id, newName: ''); final updatedTeam = await database.teamDao.getTeamById( teamId: testTeam1.id, @@ -350,9 +349,7 @@ void main() { await database.matchDao.addMatch(match: match2); // Add teams to database - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam3], - ); + await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam3]); // Associate players with teams through match1 // testTeam1: player1, player2 @@ -360,13 +357,11 @@ void main() { playerId: testPlayer1.id, matchId: match1.id, teamId: testTeam1.id, - score: 0, ); await database.playerMatchDao.addPlayerToMatch( playerId: testPlayer2.id, matchId: match1.id, teamId: testTeam1.id, - score: 0, ); // Associate players with teams through match2 @@ -375,13 +370,11 @@ void main() { playerId: testPlayer1.id, matchId: match2.id, teamId: testTeam3.id, - score: 0, ); await database.playerMatchDao.addPlayerToMatch( playerId: testPlayer3.id, matchId: match2.id, teamId: testTeam3.id, - score: 0, ); final team1 = await database.teamDao.getTeamById(teamId: testTeam1.id); @@ -420,10 +413,11 @@ void main() { final allTeams = await database.teamDao.getAllTeams(); expect(allTeams.length, 3); - expect( - allTeams.map((t) => t.id).toSet(), - {testTeam1.id, testTeam2.id, testTeam3.id}, - ); + expect(allTeams.map((t) => t.id).toSet(), { + testTeam1.id, + testTeam2.id, + testTeam3.id, + }); }); // Verifies that teamExists returns false for deleted teams. @@ -462,9 +456,7 @@ void main() { // Verifies that addTeam after deleteAllTeams works correctly. test('Adding team after deleteAllTeams works correctly', () async { - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam2], - ); + await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam2]); expect(await database.teamDao.getTeamCount(), 2); await database.teamDao.deleteAllTeams(); @@ -524,4 +516,4 @@ void main() { expect(fetchedTeam.createdAt, testTeam1.createdAt); }); }); -} \ No newline at end of file +} diff --git a/test/db_tests/entities/game_test.dart b/test/db_tests/entities/game_test.dart index 924a60b..b00dba1 100644 --- a/test/db_tests/entities/game_test.dart +++ b/test/db_tests/entities/game_test.dart @@ -4,7 +4,7 @@ import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/models/game.dart'; void main() { late AppDatabase database; @@ -54,7 +54,6 @@ void main() { }); group('Game Tests', () { - // Verifies that getAllGames returns an empty list when the database has no games. test('getAllGames returns empty list when no games exist', () async { final allGames = await database.gameDao.getAllGames(); @@ -106,7 +105,7 @@ void main() { // Verifies that getGameById throws a StateError when the game doesn't exist. test('getGameById throws exception for non-existent game', () async { expect( - () => database.gameDao.getGameById(gameId: 'non-existent-id'), + () => database.gameDao.getGameById(gameId: 'non-existent-id'), throwsA(isA()), ); }); @@ -134,7 +133,13 @@ void main() { // Verifies that a game with empty optional fields can be added and retrieved. test('addGame handles game with null optional fields', () async { - final gameWithNulls = Game(name: 'Simple Game', ruleset: Ruleset.lowestScore, description: 'A simple game', color: GameColor.green, icon: ''); + final gameWithNulls = Game( + name: 'Simple Game', + ruleset: Ruleset.lowestScore, + description: 'A simple game', + color: GameColor.green, + icon: '', + ); final result = await database.gameDao.addGame(game: gameWithNulls); expect(result, true); @@ -419,9 +424,7 @@ void main() { // Verifies that getGameCount updates correctly after deleting a game. test('getGameCount updates correctly after deletion', () async { - await database.gameDao.addGamesAsList( - games: [testGame1, testGame2], - ); + await database.gameDao.addGamesAsList(games: [testGame1, testGame2]); final countBefore = await database.gameDao.getGameCount(); expect(countBefore, 2); diff --git a/test/db_tests/entities/player_test.dart b/test/db_tests/entities/player_test.dart index 9d7fa77..3042b33 100644 --- a/test/db_tests/entities/player_test.dart +++ b/test/db_tests/entities/player_test.dart @@ -3,7 +3,7 @@ import 'package:drift/drift.dart' hide isNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/player.dart'; void main() { late AppDatabase database; @@ -35,7 +35,6 @@ void main() { }); group('Player Tests', () { - // Verifies that players can be added and retrieved with all fields intact. test('Adding and fetching single player works correctly', () async { await database.playerDao.addPlayer(player: testPlayer1); @@ -264,16 +263,22 @@ void main() { }); // 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: ''); + test( + 'Player with special characters in name is stored correctly', + () async { + final specialPlayer = Player( + name: 'Test!@#\$%^&*()_+-=[]{}|;\':",.<>?/`~', + description: '', + ); - await database.playerDao.addPlayer(player: specialPlayer); + await database.playerDao.addPlayer(player: specialPlayer); - final fetchedPlayer = await database.playerDao.getPlayerById( - playerId: specialPlayer.id, - ); - expect(fetchedPlayer.name, specialPlayer.name); - }); + final fetchedPlayer = await database.playerDao.getPlayerById( + playerId: specialPlayer.id, + ); + expect(fetchedPlayer.name, specialPlayer.name); + }, + ); // Verifies that a player with description is stored correctly. test('Player with description is stored correctly', () async { @@ -293,7 +298,10 @@ void main() { // Verifies that a player with null description is stored correctly. test('Player with null description is stored correctly', () async { - final playerWithoutDescription = Player(name: 'No Description Player', description: ''); + final playerWithoutDescription = Player( + name: 'No Description Player', + description: '', + ); await database.playerDao.addPlayer(player: playerWithoutDescription); diff --git a/test/db_tests/relationships/player_group_test.dart b/test/db_tests/relationships/player_group_test.dart index 051daf8..7004e17 100644 --- a/test/db_tests/relationships/player_group_test.dart +++ b/test/db_tests/relationships/player_group_test.dart @@ -3,8 +3,8 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/models/group.dart'; +import 'package:tallee/data/models/player.dart'; void main() { late AppDatabase database; @@ -42,7 +42,6 @@ void main() { }); group('Player-Group Tests', () { - // Verifies that a player can be added to an existing group and isPlayerInGroup returns true. test('Adding a player to a group works correctly', () async { await database.groupDao.addGroup(group: testGroup); @@ -127,67 +126,83 @@ void main() { }); // 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); + test( + 'addPlayerToGroup returns false when player already in group', + () async { + await database.groupDao.addGroup(group: testGroup); - // testPlayer1 is already in testGroup via group creation - final result = await database.playerGroupDao.addPlayerToGroup( - player: testPlayer1, - groupId: testGroup.id, - ); + // testPlayer1 is already in testGroup via group creation + final result = await database.playerGroupDao.addPlayerToGroup( + player: testPlayer1, + groupId: testGroup.id, + ); - expect(result, false); - }); + 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); + 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); + // testPlayer4 is not in the database yet + var playerExists = await database.playerDao.playerExists( + playerId: testPlayer4.id, + ); + expect(playerExists, false); - await database.playerGroupDao.addPlayerToGroup( - player: testPlayer4, - groupId: testGroup.id, - ); + await database.playerGroupDao.addPlayerToGroup( + player: testPlayer4, + groupId: testGroup.id, + ); - // Now player should exist in player table - playerExists = await database.playerDao.playerExists( - playerId: testPlayer4.id, - ); - expect(playerExists, true); - }); + // Now player should exist in player table + playerExists = await database.playerDao.playerExists( + playerId: testPlayer4.id, + ); + expect(playerExists, true); + }, + ); // Verifies that removePlayerFromGroup returns false for non-existent player. - test('removePlayerFromGroup returns false for non-existent player', () async { - await database.groupDao.addGroup(group: testGroup); + test( + 'removePlayerFromGroup returns false for non-existent player', + () async { + await database.groupDao.addGroup(group: testGroup); - final result = await database.playerGroupDao.removePlayerFromGroup( - playerId: 'non-existent-player-id', - groupId: testGroup.id, - ); + final result = await database.playerGroupDao.removePlayerFromGroup( + playerId: 'non-existent-player-id', + groupId: testGroup.id, + ); - expect(result, false); - }); + expect(result, false); + }, + ); // Verifies that removePlayerFromGroup returns false for non-existent group. - test('removePlayerFromGroup returns false for non-existent group', () async { - await database.playerDao.addPlayer(player: testPlayer1); + test( + 'removePlayerFromGroup returns false for non-existent group', + () async { + await database.playerDao.addPlayer(player: testPlayer1); - final result = await database.playerGroupDao.removePlayerFromGroup( - playerId: testPlayer1.id, - groupId: 'non-existent-group-id', - ); + final result = await database.playerGroupDao.removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: 'non-existent-group-id', + ); - expect(result, false); - }); + expect(result, false); + }, + ); // Verifies that getPlayersOfGroup returns empty list for group with no members. test('getPlayersOfGroup returns empty list for empty group', () async { - final emptyGroup = Group(name: 'Empty Group', description: '', members: []); + final emptyGroup = Group( + name: 'Empty Group', + description: '', + members: [], + ); await database.groupDao.addGroup(group: emptyGroup); final players = await database.playerGroupDao.getPlayersOfGroup( @@ -198,13 +213,16 @@ void main() { }); // 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', - ); + test( + 'getPlayersOfGroup returns empty list for non-existent group', + () async { + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: 'non-existent-group-id', + ); - expect(players, isEmpty); - }); + 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 { @@ -231,7 +249,11 @@ void main() { // Verifies that a player can be in multiple groups. test('Player can be in multiple groups', () async { - final secondGroup = Group(name: 'Second Group', description: '', members: []); + final secondGroup = Group( + name: 'Second Group', + description: '', + members: [], + ); await database.groupDao.addGroup(group: testGroup); await database.groupDao.addGroup(group: secondGroup); @@ -255,29 +277,36 @@ void main() { }); // 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); + 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, - ); + // 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, - ); + 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); - }); + expect(inFirstGroup, false); + expect(inSecondGroup, true); + }, + ); // Verifies that addPlayerToGroup returns true on successful addition. test('addPlayerToGroup returns true on successful addition', () async { @@ -293,21 +322,26 @@ void main() { }); // Verifies that removing the same player twice returns false on second attempt. - test('Removing same player twice returns false on second attempt', () async { - await database.groupDao.addGroup(group: testGroup); + test( + 'Removing same player twice returns false on second attempt', + () async { + await database.groupDao.addGroup(group: testGroup); - final firstRemoval = await database.playerGroupDao.removePlayerFromGroup( - playerId: testPlayer1.id, - groupId: testGroup.id, - ); - expect(firstRemoval, true); + final firstRemoval = await database.playerGroupDao + .removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: testGroup.id, + ); + expect(firstRemoval, true); - final secondRemoval = await database.playerGroupDao.removePlayerFromGroup( - playerId: testPlayer1.id, - groupId: testGroup.id, - ); - expect(secondRemoval, false); - }); + final secondRemoval = await database.playerGroupDao + .removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: testGroup.id, + ); + expect(secondRemoval, false); + }, + ); // Verifies that replaceGroupPlayers removes all existing players and replaces with new list. test('replaceGroupPlayers replaces all group members correctly', () async { diff --git a/test/db_tests/relationships/player_match_test.dart b/test/db_tests/relationships/player_match_test.dart index 890e74e..3db48de 100644 --- a/test/db_tests/relationships/player_match_test.dart +++ b/test/db_tests/relationships/player_match_test.dart @@ -4,11 +4,11 @@ import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/group.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.dart'; -import 'package:tallee/data/dto/team.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/player.dart'; +import 'package:tallee/data/models/team.dart'; void main() { late AppDatabase database; @@ -48,7 +48,13 @@ void main() { description: '', members: [testPlayer1, testPlayer2, testPlayer3], ); - testGame = Game(name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', color: GameColor.blue, icon: ''); + testGame = Game( + name: 'Test Game', + ruleset: Ruleset.singleWinner, + description: 'A test game', + color: GameColor.blue, + icon: '', + ); testMatchOnlyGroup = Match( name: 'Test Match with Group', game: testGame, @@ -61,14 +67,8 @@ void main() { players: [testPlayer4, testPlayer5, testPlayer6], notes: '', ); - testTeam1 = Team( - name: 'Team Alpha', - members: [testPlayer1, testPlayer2], - ); - testTeam2 = Team( - name: 'Team Beta', - members: [testPlayer3, testPlayer4], - ); + testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]); + testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]); }); await database.playerDao.addPlayersAsList( players: [ @@ -88,8 +88,6 @@ void main() { }); group('Player-Match Tests', () { - - // Verifies that matchHasPlayers returns false initially and true after adding a player. test('Match has player works correctly', () async { await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.playerDao.addPlayer(player: testPlayer1); @@ -112,7 +110,6 @@ void main() { expect(matchHasPlayers, true); }); - // Verifies that a player can be added to a match and isPlayerInMatch returns true. test('Adding a player to a match works correctly', () async { await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.playerDao.addPlayer(player: testPlayer5); @@ -136,7 +133,6 @@ void main() { expect(playerAdded, false); }); - // Verifies that a player can be removed from a match and the player count decreases. test('Removing player from match works correctly', () async { await database.matchDao.addMatch(match: testMatchOnlyPlayers); @@ -153,30 +149,25 @@ void main() { ); expect(result.players.length, testMatchOnlyPlayers.players.length - 1); - final playerExists = result.players.any( - (p) => p.id == playerToRemove.id, - ); + final playerExists = result.players.any((p) => p.id == playerToRemove.id); expect(playerExists, false); }); - // Verifies that getPlayersOfMatch returns all players of a match with correct data. test('Retrieving players of a match works correctly', () async { await database.matchDao.addMatch(match: testMatchOnlyPlayers); - final players = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, - ) ?? []; + 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, - ); + expect(players[i].createdAt, testMatchOnlyPlayers.players[i].createdAt); } }); - // Verifies that updatePlayersFromMatch replaces all existing players with new ones. test('Updating the match players works correctly', () async { await database.matchDao.addMatch(match: testMatchOnlyPlayers); @@ -220,13 +211,22 @@ void main() { } }); - // Verifies that the same player can be added to multiple different matches. test( 'Adding the same player to separate matches works correctly', - () async { + () async { final playersList = [testPlayer1, testPlayer2, testPlayer3]; - final match1 = Match(name: 'Match 1', game: testGame, players: playersList, notes: ''); - final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); + final match1 = Match( + name: 'Match 1', + game: testGame, + players: playersList, + notes: '', + ); + final match2 = Match( + name: 'Match 2', + game: testGame, + players: playersList, + notes: '', + ); await Future.wait([ database.matchDao.addMatch(match: match1), @@ -267,83 +267,6 @@ void main() { expect(players, isNull); }); - // Verifies that adding a player with initial score works correctly. - test('Adding player with initial score works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - score: 100, - ); - - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - ); - - expect(score, 100); - }); - - // Verifies that getPlayerScore returns the correct score. - test('getPlayerScore returns correct score', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - // Default score should be 0 when added through match - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - ); - - expect(score, 0); - }); - - // Verifies that getPlayerScore returns null for non-existent player-match combination. - test('getPlayerScore returns null for non-existent player in match', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyGroup.id, - playerId: 'non-existent-player-id', - ); - - expect(score, isNull); - }); - - // Verifies that updatePlayerScore updates the score correctly. - test('updatePlayerScore updates score correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - final updated = await database.playerMatchDao.updatePlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - newScore: 50, - ); - - expect(updated, true); - - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - ); - - expect(score, 50); - }); - - // Verifies that updatePlayerScore returns false for non-existent player-match. - test('updatePlayerScore returns false for non-existent player-match', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - - final updated = await database.playerMatchDao.updatePlayerScore( - matchId: testMatchOnlyGroup.id, - playerId: 'non-existent-player-id', - newScore: 50, - ); - - expect(updated, false); - }); - - // Verifies that adding a player with teamId works correctly. test('Adding player with teamId works correctly', () async { await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.teamDao.addTeam(team: testTeam1); @@ -363,7 +286,6 @@ void main() { expect(playersInTeam[0].id, testPlayer1.id); }); - // Verifies that updatePlayerTeam updates the team correctly. test('updatePlayerTeam updates team correctly', () async { await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.teamDao.addTeam(team: testTeam1); @@ -402,7 +324,6 @@ void main() { expect(playersInTeam1.isEmpty, true); }); - // Verifies that updatePlayerTeam can set team to null. test('updatePlayerTeam can remove player from team', () async { await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.teamDao.addTeam(team: testTeam1); @@ -430,18 +351,20 @@ void main() { expect(playersInTeam.isEmpty, true); }); - // Verifies that updatePlayerTeam returns false for non-existent player-match. - test('updatePlayerTeam returns false for non-existent player-match', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); + 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, - ); + final updated = await database.playerMatchDao.updatePlayerTeam( + matchId: testMatchOnlyGroup.id, + playerId: 'non-existent-player-id', + teamId: testTeam1.id, + ); - expect(updated, false); - }); + expect(updated, false); + }, + ); // Verifies that getPlayersInTeam returns empty list for non-existent team. test('getPlayersInTeam returns empty list for non-existent team', () async { @@ -455,7 +378,6 @@ void main() { expect(players.isEmpty, true); }); - // Verifies that getPlayersInTeam returns all players of a team. test('getPlayersInTeam returns all players of a team', () async { await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.teamDao.addTeam(team: testTeam1); @@ -482,43 +404,34 @@ void main() { expect(playerIds.contains(testPlayer2.id), true); }); - // Verifies that removePlayerFromMatch returns false for non-existent player. - test('removePlayerFromMatch returns false for non-existent player', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); + test( + 'removePlayerFromMatch returns false for non-existent player', + () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); - final removed = await database.playerMatchDao.removePlayerFromMatch( - playerId: 'non-existent-player-id', - matchId: testMatchOnlyPlayers.id, - ); + final removed = await database.playerMatchDao.removePlayerFromMatch( + playerId: 'non-existent-player-id', + matchId: testMatchOnlyPlayers.id, + ); - expect(removed, false); - }); + expect(removed, false); + }, + ); - // Verifies that adding the same player twice to the same match is ignored. 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, - score: 10, ); // Try to add the same player again with different score await database.playerMatchDao.addPlayerToMatch( matchId: testMatchOnlyGroup.id, playerId: testPlayer1.id, - score: 100, ); - // Score should still be 10 because insert was ignored - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - ); - - expect(score, 10); - // Verify player count is still 1 final players = await database.playerMatchDao.getPlayersOfMatch( matchId: testMatchOnlyGroup.id, @@ -527,30 +440,31 @@ void main() { expect(players?.length, 1); }); - // Verifies that updatePlayersFromMatch with empty list removes all players. - test('updatePlayersFromMatch with empty list removes all players', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); + 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); + // Verify players exist initially + var players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, + ); + expect(players?.length, 3); - // Update with empty list - await database.playerMatchDao.updatePlayersFromMatch( - matchId: testMatchOnlyPlayers.id, - newPlayer: [], - ); + // Update with empty list + await database.playerMatchDao.updatePlayersFromMatch( + matchId: testMatchOnlyPlayers.id, + newPlayer: [], + ); - // Verify all players are removed - players = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, - ); - expect(players, isNull); - }); + // Verify all players are removed + players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, + ); + expect(players, isNull); + }, + ); - // Verifies that updatePlayersFromMatch with same players makes no changes. test('updatePlayersFromMatch with same players makes no changes', () async { await database.matchDao.addMatch(match: testMatchOnlyPlayers); @@ -572,7 +486,6 @@ void main() { } }); - // Verifies that matchHasPlayers returns false for non-existent match. test('matchHasPlayers returns false for non-existent match', () async { final hasPlayers = await database.playerMatchDao.matchHasPlayers( matchId: 'non-existent-match-id', @@ -581,7 +494,6 @@ void main() { expect(hasPlayers, false); }); - // Verifies that isPlayerInMatch returns false for non-existent match. test('isPlayerInMatch returns false for non-existent match', () async { final isInMatch = await database.playerMatchDao.isPlayerInMatch( matchId: 'non-existent-match-id', @@ -591,127 +503,20 @@ void main() { expect(isInMatch, false); }); - // Verifies that updatePlayersFromMatch preserves scores for existing players. - test('updatePlayersFromMatch only modifies player associations', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - // Update score for existing player - await database.playerMatchDao.updatePlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - newScore: 75, - ); - - // Update players, keeping testPlayer4 and adding testPlayer1 - await database.playerMatchDao.updatePlayersFromMatch( - matchId: testMatchOnlyPlayers.id, - newPlayer: [testPlayer4, testPlayer1], - ); - - // Verify testPlayer4's score is preserved - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - ); - - expect(score, 75); - - // Verify testPlayer1 was added with default score - final newPlayerScore = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer1.id, - ); - - expect(newPlayerScore, 0); - }); - - // Verifies that adding a player with both score and teamId works correctly. - test('Adding player with score and 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, - score: 150, - ); - - // Verify score - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - ); - expect(score, 150); - - // Verify team assignment - final playersInTeam = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam1.id, - ); - expect(playersInTeam.length, 1); - expect(playersInTeam[0].id, testPlayer1.id); - }); - - // Verifies that updating score with negative value works. - test('updatePlayerScore with negative score works', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - final updated = await database.playerMatchDao.updatePlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - newScore: -10, - ); - - expect(updated, true); - - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - ); - - expect(score, -10); - }); - - // Verifies that updating score with zero value works. - test('updatePlayerScore with zero score works', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - // First set a non-zero score - await database.playerMatchDao.updatePlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - newScore: 100, - ); - - // Then update to zero - final updated = await database.playerMatchDao.updatePlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - newScore: 0, - ); - - expect(updated, true); - - final score = await database.playerMatchDao.getPlayerScore( - matchId: testMatchOnlyPlayers.id, - playerId: testPlayer4.id, - ); - - expect(score, 0); - }); - // 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); + 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, - ); + final players = await database.playerMatchDao.getPlayersInTeam( + matchId: 'non-existent-match-id', + teamId: testTeam1.id, + ); - expect(players.isEmpty, true); - }); + 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 { @@ -759,8 +564,18 @@ void main() { // 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, notes: ''); - final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); + final match1 = Match( + name: 'Match 1', + game: testGame, + players: playersList, + notes: '', + ); + final match2 = Match( + name: 'Match 2', + game: testGame, + players: playersList, + notes: '', + ); await Future.wait([ database.matchDao.addMatch(match: match1), @@ -789,56 +604,20 @@ void main() { expect(isInMatch2, true); }); - // Verifies that updating scores for players in different matches are independent. - test('Player scores are independent across matches', () async { - final playersList = [testPlayer1]; - final match1 = Match(name: 'Match 1', game: testGame, players: playersList, notes: ''); - final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); - - await Future.wait([ - database.matchDao.addMatch(match: match1), - database.matchDao.addMatch(match: match2), - ]); - - // Update score in match1 - await database.playerMatchDao.updatePlayerScore( - matchId: match1.id, - playerId: testPlayer1.id, - newScore: 100, - ); - - // Update score in match2 - await database.playerMatchDao.updatePlayerScore( - matchId: match2.id, - playerId: testPlayer1.id, - newScore: 50, - ); - - // Verify scores are independent - final scoreInMatch1 = await database.playerMatchDao.getPlayerScore( - matchId: match1.id, - playerId: testPlayer1.id, - ); - final scoreInMatch2 = await database.playerMatchDao.getPlayerScore( - matchId: match2.id, - playerId: testPlayer1.id, - ); - - expect(scoreInMatch1, 100); - expect(scoreInMatch2, 50); - }); - // 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), - ); - }); + 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 { diff --git a/test/db_tests/values/score_test.dart b/test/db_tests/values/score_test.dart index 2ad4671..0fd4993 100644 --- a/test/db_tests/values/score_test.dart +++ b/test/db_tests/values/score_test.dart @@ -2,11 +2,12 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart' hide isNull, isNotNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/dto/game.dart'; -import 'package:tallee/data/dto/match.dart'; -import 'package:tallee/data/dto/player.dart'; import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/models/game.dart'; +import 'package:tallee/data/models/match.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/score_entry.dart'; void main() { late AppDatabase database; @@ -32,7 +33,13 @@ void main() { testPlayer1 = Player(name: 'Alice', description: ''); testPlayer2 = Player(name: 'Bob', description: ''); testPlayer3 = Player(name: 'Charlie', description: ''); - testGame = Game(name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', color: GameColor.blue, icon: ''); + testGame = Game( + name: 'Test Game', + ruleset: Ruleset.singleWinner, + description: 'A test game', + color: GameColor.blue, + icon: '', + ); testMatch1 = Match( name: 'Test Match 1', game: testGame, @@ -60,680 +67,641 @@ void main() { }); group('Score Tests', () { + group('Adding and Fetching scores', () { + test('Single Score', () async { + ScoreEntry entry = ScoreEntry(roundNumber: 1, score: 10, change: 10); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry, + ); - // Verifies that a score can be added and retrieved with all fields intact. - test('Adding and fetching a score works correctly', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); + final score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); - final score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); + expect(score, isNotNull); + expect(score!.roundNumber, 1); + expect(score.score, 10); + expect(score.change, 10); + }); - expect(score, isNotNull); - expect(score!.playerId, testPlayer1.id); - expect(score.matchId, testMatch1.id); - expect(score.roundNumber, 1); - expect(score.score, 10); - expect(score.change, 10); + test('Multiple Scores', () 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.scoreDao.addScoresAsList( + entrys: entryList, + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + final scores = await database.scoreDao.getAllPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + expect(scores, isNotNull); + + // Scores should be returned in order of round number + for (int i = 0; i < entryList.length; i++) { + expect(scores[i].roundNumber, entryList[i].roundNumber); + expect(scores[i].score, entryList[i].score); + expect(scores[i].change, entryList[i].change); + } + }); }); - // Verifies that getScoresForMatch returns all scores for a given match. - test('Getting scores for a match works correctly', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer2.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 20, - change: 20, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 2, - score: 25, - change: 15, - ); + group('Undesirable values', () { + test('Score & Round can have negative values', () async { + ScoreEntry entry = ScoreEntry(roundNumber: -2, score: -10, change: -10); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry, + ); - final scores = await database.scoreDao.getScoresForMatch( - matchId: testMatch1.id, - ); + final score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: -2, + ); - expect(scores.length, 3); + expect(score, isNotNull); + expect(score!.roundNumber, -2); + expect(score.score, -10); + expect(score.change, -10); + }); + + test('Score & Round can have zero values', () async { + ScoreEntry entry = ScoreEntry(roundNumber: 0, score: 0, change: 0); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry, + ); + + final score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 0, + ); + + expect(score, isNotNull); + expect(score!.score, 0); + expect(score.change, 0); + }); + + test('Getting score for a non-existent entities returns null', () async { + var score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: -1, + ); + + expect(score, isNull); + + score = await database.scoreDao.getScore( + playerId: 'non-existin-player', + matchId: testMatch1.id, + ); + + expect(score, isNull); + + score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: 'non-existing-match', + ); + + expect(score, isNull); + }); + + test('Getting score for a non-match player returns null', () async { + ScoreEntry entry = ScoreEntry(roundNumber: 1, score: 10, change: 10); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry, + ); + + await database.scoreDao.addScore( + playerId: testPlayer3.id, + matchId: testMatch2.id, + entry: entry, + ); + + var score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch2.id, + roundNumber: 1, + ); + + expect(score, isNull); + }); }); - // Verifies that getPlayerScoresInMatch returns all scores for a player in a match, ordered by round. - test('Getting player scores in a match works correctly', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 2, - score: 25, - change: 15, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 3, - score: 30, - change: 5, - ); + group('Scores in matches', () { + test('getAllMatchScores()', () async { + ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); + ScoreEntry entry2 = ScoreEntry(roundNumber: 1, score: 20, change: 20); + ScoreEntry entry3 = ScoreEntry(roundNumber: 2, score: 25, change: 15); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry1, + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + entry: entry2, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry3, + ); - final playerScores = await database.scoreDao.getPlayerScoresInMatch( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); + final scores = await database.scoreDao.getAllMatchScores( + matchId: testMatch1.id, + ); - expect(playerScores.length, 3); - expect(playerScores[0].roundNumber, 1); - expect(playerScores[1].roundNumber, 2); - expect(playerScores[2].roundNumber, 3); - expect(playerScores[0].score, 10); - expect(playerScores[1].score, 25); - expect(playerScores[2].score, 30); + expect(scores.length, 2); + expect(scores[testPlayer1.id]!.length, 2); + expect(scores[testPlayer2.id]!.length, 1); + }); + + test('getAllMatchScores() with no scores saved', () async { + final scores = await database.scoreDao.getAllMatchScores( + matchId: testMatch1.id, + ); + + expect(scores.isEmpty, true); + }); + + test('getAllPlayerScoresInMatch()', () async { + ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); + ScoreEntry entry2 = ScoreEntry(roundNumber: 2, score: 25, change: 15); + ScoreEntry entry3 = ScoreEntry(roundNumber: 1, score: 30, change: 30); + await database.scoreDao.addScoresAsList( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entrys: [entry1, entry2], + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + entry: entry3, + ); + + final playerScores = await database.scoreDao.getAllPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + expect(playerScores.length, 2); + expect(playerScores[0].roundNumber, 1); + expect(playerScores[1].roundNumber, 2); + expect(playerScores[0].score, 10); + expect(playerScores[1].score, 25); + expect(playerScores[0].change, 10); + expect(playerScores[1].change, 15); + }); + + test('getAllPlayerScoresInMatch() with no scores saved', () async { + final playerScores = await database.scoreDao.getAllPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + expect(playerScores.isEmpty, true); + }); + + test('Scores are isolated across different matches', () async { + ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); + ScoreEntry entry2 = ScoreEntry(roundNumber: 1, score: 50, change: 50); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry1, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch2.id, + entry: entry2, + ); + + final match1Scores = await database.scoreDao.getAllPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + expect(match1Scores.length, 1); + expect(match1Scores[0].score, 10); + expect(match1Scores[0].change, 10); + + final match2Scores = await database.scoreDao.getAllPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch2.id, + ); + + expect(match2Scores.length, 1); + expect(match2Scores[0].score, 50); + expect(match2Scores[0].change, 50); + }); }); - // Verifies that getScoreForRound returns null for a non-existent round number. - test('Getting score for a non-existent round returns null', () async { - final score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 999, - ); + group('Updating scores', () { + test('updateScore()', () async { + ScoreEntry entry1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); + ScoreEntry entry2 = ScoreEntry(roundNumber: 2, score: 15, change: 5); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry1, + ); - expect(score, isNull); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entry2, + ); + + final updated = await database.scoreDao.updateScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + newEntry: ScoreEntry(roundNumber: 2, score: 50, change: 40), + ); + + expect(updated, true); + + final score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 2, + ); + + expect(score, isNotNull); + expect(score!.score, 50); + expect(score.change, 40); + }); + + test('Updating a non-existent score returns false', () async { + final updated = await database.scoreDao.updateScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + newEntry: ScoreEntry(roundNumber: 1, score: 20, change: 20), + ); + + expect(updated, false); + }); }); - // Verifies that updateScore correctly updates the score and change values. - test('Updating a score works correctly', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); + group('Deleting scores', () { + test('deleteScore() ', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), + ); - final updated = await database.scoreDao.updateScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - newScore: 50, - newChange: 40, - ); + final deleted = await database.scoreDao.deleteScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); - expect(updated, true); + expect(deleted, true); - final score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); + final score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); - expect(score, isNotNull); - expect(score!.score, 50); - expect(score.change, 40); + expect(score, isNull); + }); + + test('Deleting a non-existent score returns false', () async { + final deleted = await database.scoreDao.deleteScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(deleted, false); + }); + + test('deleteAllScoresForMatch() works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 20, change: 20), + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch2.id, + entry: ScoreEntry(roundNumber: 1, score: 15, change: 15), + ); + + final deleted = await database.scoreDao.deleteAllScoresForMatch( + matchId: testMatch1.id, + ); + + expect(deleted, true); + + final match1Scores = await database.scoreDao.getAllMatchScores( + matchId: testMatch1.id, + ); + expect(match1Scores.length, 0); + + final match2Scores = await database.scoreDao.getAllMatchScores( + matchId: testMatch2.id, + ); + expect(match2Scores.length, 1); + }); + + test('deleteAllScoresForPlayerInMatch() works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), + ); + + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 2, score: 15, change: 5), + ); + + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 6, change: 6), + ); + + final deleted = await database.scoreDao.deleteAllScoresForPlayerInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + expect(deleted, true); + + final player1Scores = await database.scoreDao.getAllPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + expect(player1Scores.length, 0); + + final player2Scores = await database.scoreDao.getAllPlayerScoresInMatch( + playerId: testPlayer2.id, + matchId: testMatch1.id, + ); + expect(player2Scores.length, 1); + }); }); - // Verifies that updateScore returns false for a non-existent score entry. - test('Updating a non-existent score returns false', () async { - final updated = await database.scoreDao.updateScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 999, - newScore: 50, - newChange: 40, - ); + group('Score Aggregations & Edge Cases', () { + test('getLatestRoundNumber()', () async { + var latestRound = await database.scoreDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, isNull); - expect(updated, false); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), + ); + + latestRound = await database.scoreDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, 1); + + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 5, score: 50, change: 40), + ); + + latestRound = await database.scoreDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, 5); + }); + + test('getLatestRoundNumber() with non-consecutive rounds', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 5, score: 50, change: 40), + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 3, score: 30, change: 20), + ); + + final latestRound = await database.scoreDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + + expect(latestRound, 5); + }); + + test('getTotalScoreForPlayer()', () async { + var totalScore = await database.scoreDao.getTotalScoreForPlayer( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + expect(totalScore, 0); + + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 2, score: 25, change: 15), + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 3, score: 40, change: 15), + ); + + totalScore = await database.scoreDao.getTotalScoreForPlayer( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + expect(totalScore, 40); + }); + + test('getTotalScoreForPlayer() ignores round score', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 2, score: 25, change: 25), + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 25, change: 10), + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 3, score: 25, change: 25), + ); + + final totalScore = await database.scoreDao.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.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: ScoreEntry(roundNumber: 1, score: 20, change: 20), + ); + + final score = await database.scoreDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(score, isNotNull); + expect(score!.score, 20); + expect(score.change, 20); + }); }); - // Verifies that deleteScore removes the score entry and returns true. - test('Deleting a score works correctly', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); + group('Handling Winner', () { + test('hasWinner() works correctly', () async { + var hasWinner = await database.scoreDao.hasWinner( + matchId: testMatch1.id, + ); + expect(hasWinner, false); - final deleted = await database.scoreDao.deleteScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); + await database.scoreDao.setWinner( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); - expect(deleted, true); + hasWinner = await database.scoreDao.hasWinner(matchId: testMatch1.id); + expect(hasWinner, true); + }); - final score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); + test('getWinnersForMatch() returns correct winner', () async { + var winner = await database.scoreDao.getWinner(matchId: testMatch1.id); + expect(winner, isNull); - expect(score, isNull); + await database.scoreDao.setWinner( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + winner = await database.scoreDao.getWinner(matchId: testMatch1.id); + + expect(winner, isNotNull); + expect(winner!.id, testPlayer1.id); + }); + + test('removeWinner() works correctly', () async { + var removed = await database.scoreDao.removeWinner( + matchId: testMatch1.id, + ); + expect(removed, false); + + await database.scoreDao.setWinner( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + removed = await database.scoreDao.removeWinner(matchId: testMatch1.id); + expect(removed, true); + + var winner = await database.scoreDao.getWinner(matchId: testMatch1.id); + expect(winner, isNull); + }); }); - // Verifies that deleteScore returns false for a non-existent score entry. - test('Deleting a non-existent score returns false', () async { - final deleted = await database.scoreDao.deleteScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 999, - ); + group('Handling Looser', () { + test('hasLooser() works correctly', () async { + var hasLooser = await database.scoreDao.hasLooser( + matchId: testMatch1.id, + ); + expect(hasLooser, false); - expect(deleted, false); - }); + await database.scoreDao.setLooser( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); - // Verifies that deleteScoresForMatch removes all scores for a match but keeps other match scores. - test('Deleting scores for a match works correctly', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer2.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 20, - change: 20, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch2.id, - roundNumber: 1, - score: 15, - change: 15, - ); + hasLooser = await database.scoreDao.hasLooser(matchId: testMatch1.id); + expect(hasLooser, true); + }); - final deleted = await database.scoreDao.deleteScoresForMatch( - matchId: testMatch1.id, - ); + test('getLooser() returns correct winner', () async { + var looser = await database.scoreDao.getLooser(matchId: testMatch1.id); + expect(looser, isNull); - expect(deleted, true); + await database.scoreDao.setLooser( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); - final match1Scores = await database.scoreDao.getScoresForMatch( - matchId: testMatch1.id, - ); - expect(match1Scores.length, 0); + looser = await database.scoreDao.getLooser(matchId: testMatch1.id); - final match2Scores = await database.scoreDao.getScoresForMatch( - matchId: testMatch2.id, - ); - expect(match2Scores.length, 1); - }); + expect(looser, isNotNull); + expect(looser!.id, testPlayer1.id); + }); - // Verifies that deleteScoresForPlayer removes all scores for a player across all matches. - test('Deleting scores for a player works correctly', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch2.id, - roundNumber: 1, - score: 15, - change: 15, - ); - await database.scoreDao.addScore( - playerId: testPlayer2.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 20, - change: 20, - ); + test('removeLooser() works correctly', () async { + var removed = await database.scoreDao.removeLooser( + matchId: testMatch1.id, + ); + expect(removed, false); - final deleted = await database.scoreDao.deleteScoresForPlayer( - playerId: testPlayer1.id, - ); + await database.scoreDao.setLooser( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); - expect(deleted, true); + removed = await database.scoreDao.removeLooser(matchId: testMatch1.id); + expect(removed, true); - final player1Scores = await database.scoreDao.getPlayerScoresInMatch( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); - expect(player1Scores.length, 0); - - final player2Scores = await database.scoreDao.getPlayerScoresInMatch( - playerId: testPlayer2.id, - matchId: testMatch1.id, - ); - expect(player2Scores.length, 1); - }); - - // Verifies that getLatestRoundNumber returns the highest round number for a match. - test('Getting latest round number works correctly', () async { - var latestRound = await database.scoreDao.getLatestRoundNumber( - matchId: testMatch1.id, - ); - expect(latestRound, 0); - - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - - latestRound = await database.scoreDao.getLatestRoundNumber( - matchId: testMatch1.id, - ); - expect(latestRound, 1); - - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 5, - score: 50, - change: 40, - ); - - latestRound = await database.scoreDao.getLatestRoundNumber( - matchId: testMatch1.id, - ); - expect(latestRound, 5); - }); - - // Verifies that getTotalScoreForPlayer returns the latest score (cumulative) for a player. - test('Getting total score for a player works correctly', () async { - var totalScore = await database.scoreDao.getTotalScoreForPlayer( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); - expect(totalScore, 0); - - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 2, - score: 25, - change: 15, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 3, - score: 40, - change: 15, - ); - - totalScore = await database.scoreDao.getTotalScoreForPlayer( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); - expect(totalScore, 40); - }); - - // Verifies that adding a score with the same player/match/round replaces the existing one. - test('Adding the same score twice replaces the existing one', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 99, - change: 99, - ); - - final score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); - - expect(score, isNotNull); - expect(score!.score, 99); - expect(score.change, 99); - }); - - // Verifies that getScoresForMatch returns empty list for match with no scores. - test('Getting scores for match with no scores returns empty list', () async { - final scores = await database.scoreDao.getScoresForMatch( - matchId: testMatch1.id, - ); - - expect(scores.isEmpty, true); - }); - - // Verifies that getPlayerScoresInMatch returns empty list when player has no scores. - test('Getting player scores with no scores returns empty list', () async { - final playerScores = await database.scoreDao.getPlayerScoresInMatch( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); - - expect(playerScores.isEmpty, true); - }); - - // Verifies that scores can have negative values. - test('Score can have negative values', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: -10, - change: -10, - ); - - final score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); - - expect(score, isNotNull); - expect(score!.score, -10); - expect(score.change, -10); - }); - - // Verifies that scores can have zero values. - test('Score can have zero values', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 0, - change: 0, - ); - - final score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); - - expect(score, isNotNull); - expect(score!.score, 0); - expect(score.change, 0); - }); - - // Verifies that very large round numbers are supported. - test('Score supports very large round numbers', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 999999, - score: 100, - change: 100, - ); - - final score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 999999, - ); - - expect(score, isNotNull); - expect(score!.roundNumber, 999999); - }); - - // Verifies that getLatestRoundNumber returns max correctly for non-consecutive rounds. - test('Getting latest round number with non-consecutive rounds', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 5, - score: 50, - change: 40, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 3, - score: 30, - change: 20, - ); - - final latestRound = await database.scoreDao.getLatestRoundNumber( - matchId: testMatch1.id, - ); - - expect(latestRound, 5); - }); - - // Verifies that deleteScoresForMatch returns false when no scores exist. - test('Deleting scores for empty match returns false', () async { - final deleted = await database.scoreDao.deleteScoresForMatch( - matchId: testMatch1.id, - ); - - expect(deleted, false); - }); - - // Verifies that deleteScoresForPlayer returns false when player has no scores. - test('Deleting scores for player with no scores returns false', () async { - final deleted = await database.scoreDao.deleteScoresForPlayer( - playerId: testPlayer1.id, - ); - - expect(deleted, false); - }); - - // Verifies that multiple players in same match can have independent score updates. - test('Multiple players in same match have independent scores', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer2.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 20, - change: 20, - ); - - await database.scoreDao.updateScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - newScore: 100, - newChange: 90, - ); - - final player1Score = await database.scoreDao.getScoreForRound( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); - final player2Score = await database.scoreDao.getScoreForRound( - playerId: testPlayer2.id, - matchId: testMatch1.id, - roundNumber: 1, - ); - - expect(player1Score!.score, 100); - expect(player2Score!.score, 20); - }); - - // Verifies that scores are isolated across different matches. - test('Scores are isolated across different matches', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch2.id, - roundNumber: 1, - score: 50, - change: 50, - ); - - final match1Scores = await database.scoreDao.getPlayerScoresInMatch( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); - final match2Scores = await database.scoreDao.getPlayerScoresInMatch( - playerId: testPlayer1.id, - matchId: testMatch2.id, - ); - - expect(match1Scores.length, 1); - expect(match2Scores.length, 1); - expect(match1Scores[0].score, 10); - expect(match2Scores[0].score, 50); - }); - - // Verifies that getTotalScoreForPlayer returns latest score across multiple rounds. - test('Total score for player returns latest cumulative score', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 2, - score: 25, - change: 25, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 3, - score: 50, - change: 25, - ); - - final totalScore = await database.scoreDao.getTotalScoreForPlayer( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); - - // Should return the highest round's score - expect(totalScore, 50); - }); - - // Verifies that updating one player's score doesn't affect another player's score in same round. - test('Updating one player score does not affect other players in same round', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer2.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 20, - change: 20, - ); - await database.scoreDao.addScore( - playerId: testPlayer3.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 30, - change: 30, - ); - - await database.scoreDao.updateScore( - playerId: testPlayer2.id, - matchId: testMatch1.id, - roundNumber: 1, - newScore: 99, - newChange: 89, - ); - - final scores = await database.scoreDao.getScoresForMatch( - matchId: testMatch1.id, - ); - - expect(scores.length, 3); - expect(scores.where((s) => s.playerId == testPlayer1.id).first.score, 10); - expect(scores.where((s) => s.playerId == testPlayer2.id).first.score, 99); - expect(scores.where((s) => s.playerId == testPlayer3.id).first.score, 30); - }); - - // Verifies that deleting a player's scores only affects that specific player. - test('Deleting player scores only affects target player', () async { - await database.scoreDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 10, - change: 10, - ); - await database.scoreDao.addScore( - playerId: testPlayer2.id, - matchId: testMatch1.id, - roundNumber: 1, - score: 20, - change: 20, - ); - - await database.scoreDao.deleteScoresForPlayer( - playerId: testPlayer1.id, - ); - - final match1Scores = await database.scoreDao.getScoresForMatch( - matchId: testMatch1.id, - ); - - expect(match1Scores.length, 1); - expect(match1Scores[0].playerId, testPlayer2.id); + var looser = await database.scoreDao.getLooser(matchId: testMatch1.id); + expect(looser, isNull); + }); }); }); } diff --git a/test/services/data_transfer_service_test.dart b/test/services/data_transfer_service_test.dart new file mode 100644 index 0000000..bfbb09f --- /dev/null +++ b/test/services/data_transfer_service_test.dart @@ -0,0 +1,868 @@ +import 'dart:convert'; + +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart' hide isNull, isNotNull; +import 'package:drift/native.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.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/player.dart'; +import 'package:tallee/data/models/score_entry.dart'; +import 'package:tallee/data/models/team.dart'; +import 'package:tallee/services/data_transfer_service.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Game testGame; + late Group testGroup; + late Team testTeam; + late Match testMatch; + final fixedDate = DateTime(2025, 11, 19, 0, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + testPlayer1 = Player(name: 'Alice', description: 'First test player'); + testPlayer2 = Player(name: 'Bob', description: 'Second test player'); + testPlayer3 = Player(name: 'Charlie', description: 'Third player'); + + testGame = Game( + name: 'Chess', + ruleset: Ruleset.singleWinner, + description: 'Strategic board game', + color: GameColor.blue, + icon: 'chess_icon', + ); + + testGroup = Group( + name: 'Test Group', + description: 'Group for testing', + members: [testPlayer1, testPlayer2], + ); + + testTeam = Team(name: 'Test Team', members: [testPlayer1, testPlayer2]); + + testMatch = Match( + name: 'Test Match', + game: testGame, + group: testGroup, + players: [testPlayer1, testPlayer2], + notes: 'Test notes', + scores: { + testPlayer1.id: [ + ScoreEntry(roundNumber: 1, score: 10, change: 10), + ScoreEntry(roundNumber: 2, score: 20, change: 10), + ], + testPlayer2.id: [ + ScoreEntry(roundNumber: 1, score: 15, change: 15), + ScoreEntry(roundNumber: 2, score: 25, change: 10), + ], + }, + ); + }); + }); + + tearDown(() async { + await database.close(); + }); + + // Helper for getting BuildContext + Future getContext(WidgetTester tester) async { + // Minimal widget with Provider + await tester.pumpWidget( + Provider.value( + value: database, + child: MaterialApp( + home: Builder( + builder: (context) { + return Container(); + }, + ), + ), + ), + ); + final BuildContext context = tester.element(find.byType(Container)); + return context; + } + + group('DataTransferService Tests', () { + testWidgets('deleteAllData()', (tester) async { + 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(); + var gameCount = await database.gameDao.getGameCount(); + var groupCount = await database.groupDao.getGroupCount(); + var teamCount = await database.teamDao.getTeamCount(); + var matchCount = await database.matchDao.getMatchCount(); + + expect(playerCount, greaterThan(0)); + expect(gameCount, greaterThan(0)); + expect(groupCount, greaterThan(0)); + expect(teamCount, greaterThan(0)); + expect(matchCount, greaterThan(0)); + + final ctx = await getContext(tester); + await DataTransferService.deleteAllData(ctx); + + playerCount = await database.playerDao.getPlayerCount(); + gameCount = await database.gameDao.getGameCount(); + groupCount = await database.groupDao.getGroupCount(); + teamCount = await database.teamDao.getTeamCount(); + matchCount = await database.matchDao.getMatchCount(); + + expect(playerCount, 0); + expect(gameCount, 0); + expect(groupCount, 0); + expect(teamCount, 0); + expect(matchCount, 0); + }); + + group('getAppDataAsJson()', () { + group('Whole export', () { + testWidgets('Exporting app data works correctly', (tester) async { + await database.playerDao.addPlayer(player: testPlayer1); + 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); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + + expect(jsonString, isNotEmpty); + + final decoded = json.decode(jsonString) as Map; + + expect(decoded.containsKey('players'), true); + expect(decoded.containsKey('games'), true); + expect(decoded.containsKey('groups'), true); + expect(decoded.containsKey('teams'), true); + expect(decoded.containsKey('matches'), true); + + final players = decoded['players'] as List; + final games = decoded['games'] as List; + final groups = decoded['groups'] as List; + final teams = decoded['teams'] as List; + final matches = decoded['matches'] as List; + + expect(players.length, 2); + expect(games.length, 1); + expect(groups.length, 1); + expect(teams.length, 1); + expect(matches.length, 1); + }); + + testWidgets('Exporting empty data works correctly', (tester) async { + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + + final decoded = json.decode(jsonString) as Map; + + final players = decoded['players'] as List; + final games = decoded['games'] as List; + final groups = decoded['groups'] as List; + final teams = decoded['teams'] as List; + final matches = decoded['matches'] as List; + + expect(players, isEmpty); + expect(games, isEmpty); + expect(groups, isEmpty); + expect(teams, isEmpty); + expect(matches, isEmpty); + }); + }); + + group('Checking specific data', () { + testWidgets('Player data is correct', (tester) async { + await database.playerDao.addPlayer(player: testPlayer1); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final players = decoded['players'] as List; + final playerData = players[0] as Map; + + expect(playerData['id'], testPlayer1.id); + expect(playerData['name'], testPlayer1.name); + expect(playerData['description'], testPlayer1.description); + expect( + playerData['createdAt'], + testPlayer1.createdAt.toIso8601String(), + ); + }); + + testWidgets('Game data is correct', (tester) async { + await database.gameDao.addGame(game: testGame); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final games = decoded['games'] as List; + final gameData = games[0] as Map; + + expect(gameData['id'], testGame.id); + expect(gameData['name'], testGame.name); + expect(gameData['ruleset'], testGame.ruleset.name); + expect(gameData['description'], testGame.description); + expect(gameData['color'], testGame.color.name); + expect(gameData['icon'], testGame.icon); + }); + + testWidgets('Group data is correct', (tester) async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer2); + await database.groupDao.addGroup(group: testGroup); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final groups = decoded['groups'] as List; + final groupData = groups[0] as Map; + + expect(groupData['id'], testGroup.id); + expect(groupData['name'], testGroup.name); + expect(groupData['description'], testGroup.description); + expect(groupData['memberIds'], isA()); + + final memberIds = groupData['memberIds'] as List; + expect(memberIds.length, 2); + 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; + final teams = decoded['teams'] as List; + + expect(teams.length, 1); + + final teamData = teams[0] as Map; + + expect(teamData['id'], testTeam.id); + expect(teamData['name'], testTeam.name); + expect(teamData['memberIds'], isA()); + + // 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; + expect(memberIds, isEmpty); + }); + + testWidgets('Match data is correct', (tester) async { + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2], + ); + await database.gameDao.addGame(game: testGame); + await database.groupDao.addGroup(group: testGroup); + await database.matchDao.addMatch(match: testMatch); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final matches = decoded['matches'] as List; + final matchData = matches[0] as Map; + + expect(matchData['id'], testMatch.id); + expect(matchData['name'], testMatch.name); + expect(matchData['gameId'], testGame.id); + expect(matchData['groupId'], testGroup.id); + expect(matchData['playerIds'], isA()); + expect(matchData['notes'], testMatch.notes); + + // Check player ids + final playerIds = matchData['playerIds'] as List; + expect(playerIds.length, 2); + expect(playerIds, containsAll([testPlayer1.id, testPlayer2.id])); + + // Check scores structure + final scoresJson = matchData['scores'] as Map; + expect(scoresJson, isA>()); + + final scores = scoresJson.map( + (playerId, scoreList) => MapEntry( + playerId, + (scoreList as List) + .map((s) => ScoreEntry.fromJson(s as Map)) + .toList(), + ), + ); + + expect(scores, isA>>()); + + /* Player 1 scores */ + // General structure + expect(scores[testPlayer1.id], isNotNull); + expect(scores[testPlayer1.id]!.length, 2); + + // Round 1 + expect(scores[testPlayer1.id]![0].roundNumber, 1); + expect(scores[testPlayer1.id]![0].score, 10); + expect(scores[testPlayer1.id]![0].change, 10); + + // Round 2 + expect(scores[testPlayer1.id]![1].roundNumber, 2); + expect(scores[testPlayer1.id]![1].score, 20); + expect(scores[testPlayer1.id]![1].change, 10); + + /* Player 2 scores */ + // General structure + expect(scores[testPlayer2.id], isNotNull); + expect(scores[testPlayer2.id]!.length, 2); + + // Round 1 + expect(scores[testPlayer2.id]![0].roundNumber, 1); + expect(scores[testPlayer2.id]![0].score, 15); + expect(scores[testPlayer2.id]![0].change, 15); + + // Round 2 + expect(scores[testPlayer2.id]![1].roundNumber, 2); + expect(scores[testPlayer2.id]![1].score, 25); + expect(scores[testPlayer2.id]![1].change, 10); + }); + + testWidgets('Match without group is handled correctly', (tester) async { + final matchWithoutGroup = Match( + name: 'No Group Match', + game: testGame, + group: null, + players: [testPlayer1], + notes: 'No group', + ); + + await database.playerDao.addPlayer(player: testPlayer1); + await database.gameDao.addGame(game: testGame); + await database.matchDao.addMatch(match: matchWithoutGroup); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final matches = decoded['matches'] as List; + final matchData = matches[0] as Map; + + expect(matchData['groupId'], isNull); + }); + + testWidgets('Match with endedAt is handled correctly', (tester) async { + final endedDate = DateTime(2025, 12, 1, 10, 0, 0); + final endedMatch = Match( + name: 'Ended Match', + game: testGame, + players: [testPlayer1], + endedAt: endedDate, + notes: 'Finished', + ); + + await database.playerDao.addPlayer(player: testPlayer1); + await database.gameDao.addGame(game: testGame); + await database.matchDao.addMatch(match: endedMatch); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final matches = decoded['matches'] as List; + final matchData = matches[0] as Map; + + expect(matchData['endedAt'], endedDate.toIso8601String()); + }); + + testWidgets('Structure is consistent', (tester) async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.gameDao.addGame(game: testGame); + + final ctx = await getContext(tester); + final jsonString1 = await DataTransferService.getAppDataAsJson(ctx); + final jsonString2 = await DataTransferService.getAppDataAsJson(ctx); + + expect(jsonString1, equals(jsonString2)); + }); + + testWidgets('Empty match notes is handled correctly', (tester) async { + final matchWithEmptyNotes = Match( + name: 'Empty Notes Match', + game: testGame, + players: [testPlayer1], + notes: '', + ); + + await database.playerDao.addPlayer(player: testPlayer1); + await database.gameDao.addGame(game: testGame); + await database.matchDao.addMatch(match: matchWithEmptyNotes); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final matches = decoded['matches'] as List; + final matchData = matches[0] as Map; + + expect(matchData['notes'], ''); + }); + + testWidgets('Multiple players in match is handled correctly', ( + tester, + ) async { + final multiPlayerMatch = Match( + name: 'Multi Player Match', + game: testGame, + players: [testPlayer1, testPlayer2, testPlayer3], + notes: 'Three players', + ); + + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2, testPlayer3], + ); + await database.gameDao.addGame(game: testGame); + await database.matchDao.addMatch(match: multiPlayerMatch); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final matches = decoded['matches'] as List; + final matchData = matches[0] as Map; + + final playerIds = matchData['playerIds'] as List; + expect(playerIds.length, 3); + expect( + playerIds, + containsAll([testPlayer1.id, testPlayer2.id, testPlayer3.id]), + ); + }); + + testWidgets('All game colors are handled correctly', (tester) async { + final games = [ + Game( + name: 'Red Game', + ruleset: Ruleset.singleWinner, + color: GameColor.red, + icon: 'icon', + ), + Game( + name: 'Blue Game', + ruleset: Ruleset.singleWinner, + color: GameColor.blue, + icon: 'icon', + ), + Game( + name: 'Green Game', + ruleset: Ruleset.singleWinner, + color: GameColor.green, + icon: 'icon', + ), + ]; + + await database.gameDao.addGamesAsList(games: games); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final gamesJson = decoded['games'] as List; + + expect(gamesJson.length, 3); + expect( + gamesJson.map((g) => g['color']), + containsAll(['red', 'blue', 'green']), + ); + }); + + testWidgets('All rulesets are handled correctly', (tester) async { + final games = [ + Game( + name: 'Highest Score Game', + ruleset: Ruleset.highestScore, + color: GameColor.blue, + icon: 'icon', + ), + Game( + name: 'Lowest Score Game', + ruleset: Ruleset.lowestScore, + color: GameColor.blue, + icon: 'icon', + ), + Game( + name: 'Single Winner', + ruleset: Ruleset.singleWinner, + color: GameColor.blue, + icon: 'icon', + ), + ]; + + await database.gameDao.addGamesAsList(games: games); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final gamesJson = decoded['games'] as List; + + expect(gamesJson.length, 3); + expect( + gamesJson.map((g) => g['ruleset']), + containsAll(['highestScore', 'lowestScore', 'singleWinner']), + ); + }); + }); + }); + + group('Parse Methods', () { + test('parsePlayersFromJson()', () { + final jsonMap = { + 'players': [ + { + 'id': testPlayer1.id, + 'name': testPlayer1.name, + 'description': testPlayer1.description, + 'createdAt': testPlayer1.createdAt.toIso8601String(), + }, + { + 'id': testPlayer2.id, + 'name': testPlayer2.name, + 'description': testPlayer2.description, + 'createdAt': testPlayer2.createdAt.toIso8601String(), + }, + ], + }; + + final players = DataTransferService.parsePlayersFromJson(jsonMap); + + expect(players.length, 2); + expect(players[0].id, testPlayer1.id); + expect(players[0].name, testPlayer1.name); + expect(players[1].id, testPlayer2.id); + expect(players[1].name, testPlayer2.name); + }); + + test('parsePlayersFromJson() empty list', () { + final jsonMap = {'players': []}; + final players = DataTransferService.parsePlayersFromJson(jsonMap); + expect(players, isEmpty); + }); + + test('parsePlayersFromJson() missing key', () { + final jsonMap = {}; + final players = DataTransferService.parsePlayersFromJson(jsonMap); + expect(players, isEmpty); + }); + + test('parseGamesFromJson()', () { + final jsonMap = { + 'games': [ + { + 'id': testGame.id, + 'name': testGame.name, + 'ruleset': testGame.ruleset.name, + 'description': testGame.description, + 'color': testGame.color.name, + 'icon': testGame.icon, + 'createdAt': testGame.createdAt.toIso8601String(), + }, + ], + }; + + final games = DataTransferService.parseGamesFromJson(jsonMap); + + expect(games.length, 1); + expect(games[0].id, testGame.id); + expect(games[0].name, testGame.name); + expect(games[0].ruleset, testGame.ruleset); + }); + + test('parseGroupsFromJson()', () { + final playerById = { + testPlayer1.id: testPlayer1, + testPlayer2.id: testPlayer2, + }; + + final jsonMap = { + 'groups': [ + { + 'id': testGroup.id, + 'name': testGroup.name, + 'description': testGroup.description, + 'memberIds': [testPlayer1.id, testPlayer2.id], + 'createdAt': testGroup.createdAt.toIso8601String(), + }, + ], + }; + + final groups = DataTransferService.parseGroupsFromJson( + jsonMap, + playerById, + ); + + expect(groups.length, 1); + expect(groups[0].id, testGroup.id); + expect(groups[0].name, testGroup.name); + expect(groups[0].members.length, 2); + expect(groups[0].members[0].id, testPlayer1.id); + expect(groups[0].members[1].id, testPlayer2.id); + }); + + test('parseGroupsFromJson() ignores invalid player ids', () { + final playerById = {testPlayer1.id: testPlayer1}; + + final jsonMap = { + 'groups': [ + { + 'id': testGroup.id, + 'name': testGroup.name, + 'description': testGroup.description, + 'memberIds': [testPlayer1.id, 'invalid-id'], + 'createdAt': testGroup.createdAt.toIso8601String(), + }, + ], + }; + + final groups = DataTransferService.parseGroupsFromJson( + jsonMap, + playerById, + ); + + expect(groups.length, 1); + expect(groups[0].members.length, 1); + expect(groups[0].members[0].id, testPlayer1.id); + }); + + test('parseTeamsFromJson()', () { + final playerById = {testPlayer1.id: testPlayer1}; + + final jsonMap = { + 'teams': [ + { + 'id': testTeam.id, + 'name': testTeam.name, + 'memberIds': [testPlayer1.id], + 'createdAt': testTeam.createdAt.toIso8601String(), + }, + ], + }; + + final teams = DataTransferService.parseTeamsFromJson( + jsonMap, + playerById, + ); + + expect(teams.length, 1); + expect(teams[0].id, testTeam.id); + expect(teams[0].name, testTeam.name); + expect(teams[0].members.length, 1); + expect(teams[0].members[0].id, testPlayer1.id); + }); + + test('parseMatchesFromJson()', () { + final playerById = { + testPlayer1.id: testPlayer1, + testPlayer2.id: testPlayer2, + }; + final gameById = {testGame.id: testGame}; + final groupById = {testGroup.id: testGroup}; + + final jsonMap = { + 'matches': [ + { + 'id': testMatch.id, + 'name': testMatch.name, + 'gameId': testGame.id, + 'groupId': testGroup.id, + 'playerIds': [testPlayer1.id, testPlayer2.id], + 'notes': testMatch.notes, + 'createdAt': testMatch.createdAt.toIso8601String(), + }, + ], + }; + + final matches = DataTransferService.parseMatchesFromJson( + jsonMap, + gameById, + groupById, + playerById, + ); + + expect(matches.length, 1); + expect(matches[0].id, testMatch.id); + expect(matches[0].name, testMatch.name); + expect(matches[0].game.id, testGame.id); + expect(matches[0].group?.id, testGroup.id); + expect(matches[0].players.length, 2); + }); + + test('parseMatchesFromJson() creates unknown game for missing game', () { + final playerById = {testPlayer1.id: testPlayer1}; + final gameById = {}; + final groupById = {}; + + final jsonMap = { + 'matches': [ + { + 'id': testMatch.id, + 'name': testMatch.name, + 'gameId': 'non-existent-game-id', + 'playerIds': [testPlayer1.id], + 'notes': '', + 'createdAt': testMatch.createdAt.toIso8601String(), + }, + ], + }; + + final matches = DataTransferService.parseMatchesFromJson( + jsonMap, + gameById, + groupById, + playerById, + ); + + expect(matches.length, 1); + expect(matches[0].game.name, 'Unknown'); + expect(matches[0].game.ruleset, Ruleset.singleWinner); + }); + + test('parseMatchesFromJson() handles null group', () { + final playerById = {testPlayer1.id: testPlayer1}; + final gameById = {testGame.id: testGame}; + final groupById = {}; + + final jsonMap = { + 'matches': [ + { + 'id': testMatch.id, + 'name': testMatch.name, + 'gameId': testGame.id, + 'groupId': null, + 'playerIds': [testPlayer1.id], + 'notes': '', + 'createdAt': testMatch.createdAt.toIso8601String(), + }, + ], + }; + + final matches = DataTransferService.parseMatchesFromJson( + jsonMap, + gameById, + groupById, + playerById, + ); + + expect(matches.length, 1); + expect(matches[0].group, isNull); + }); + + test('parseMatchesFromJson() handles endedAt', () { + final playerById = {testPlayer1.id: testPlayer1}; + final gameById = {testGame.id: testGame}; + final groupById = {}; + final endedDate = DateTime(2025, 12, 1, 10, 0, 0); + + final jsonMap = { + 'matches': [ + { + 'id': testMatch.id, + 'name': testMatch.name, + 'gameId': testGame.id, + 'playerIds': [testPlayer1.id], + 'notes': '', + 'createdAt': testMatch.createdAt.toIso8601String(), + 'endedAt': endedDate.toIso8601String(), + }, + ], + }; + + final matches = DataTransferService.parseMatchesFromJson( + jsonMap, + gameById, + groupById, + playerById, + ); + + expect(matches.length, 1); + expect(matches[0].endedAt, endedDate); + }); + }); + + test('validateJsonSchema()', () async { + final validJson = json.encode({ + 'players': [ + { + 'id': testPlayer1.id, + 'name': testPlayer1.name, + 'description': testPlayer1.description, + 'createdAt': testPlayer1.createdAt.toIso8601String(), + }, + ], + 'games': [ + { + 'id': testGame.id, + 'name': testGame.name, + 'ruleset': testGame.ruleset.name, + 'description': testGame.description, + 'color': testGame.color.name, + 'icon': testGame.icon, + 'createdAt': testGame.createdAt.toIso8601String(), + }, + ], + 'groups': [ + { + 'id': testGroup.id, + 'name': testGroup.name, + 'description': testGroup.description, + 'memberIds': [testPlayer1.id, testPlayer2.id], + 'createdAt': testGroup.createdAt.toIso8601String(), + }, + ], + 'teams': [ + { + 'id': testTeam.id, + 'name': testTeam.name, + 'memberIds': [testPlayer1.id, testPlayer2.id], + 'createdAt': testTeam.createdAt.toIso8601String(), + }, + ], + 'matches': [ + { + 'id': testMatch.id, + 'name': testMatch.name, + 'gameId': testGame.id, + 'groupId': testGroup.id, + 'playerIds': [testPlayer1.id, testPlayer2.id], + 'notes': testMatch.notes, + 'scores': { + testPlayer1.id: [ + {'roundNumber': 1, 'score': 10, 'change': 10}, + {'roundNumber': 2, 'score': 20, 'change': 10}, + ], + testPlayer2.id: [ + {'roundNumber': 1, 'score': 15, 'change': 15}, + {'roundNumber': 2, 'score': 25, 'change': 10}, + ], + }, + 'createdAt': testMatch.createdAt.toIso8601String(), + 'endedAt': null, + }, + ], + }); + + final isValid = await DataTransferService.validateJsonSchema(validJson); + expect(isValid, true); + }); + }); +}