made icon optional and default to empty string & adjust all game instances
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 36s
Pull Request Pipeline / lint (pull_request) Successful in 46s

This commit is contained in:
2026-03-09 16:24:04 +01:00
parent 16dc9746bc
commit 43e9196dca
11 changed files with 367 additions and 254 deletions

View File

@@ -1,6 +1,6 @@
import 'package:clock/clock.dart'; import 'package:clock/clock.dart';
import 'package:uuid/uuid.dart';
import 'package:tallee/core/enums.dart'; import 'package:tallee/core/enums.dart';
import 'package:uuid/uuid.dart';
class Game { class Game {
final String id; final String id;
@@ -18,10 +18,11 @@ class Game {
required this.ruleset, required this.ruleset,
String? description, String? description,
required this.color, required this.color,
required this.icon, String? icon,
}) : id = id ?? const Uuid().v4(), }) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? clock.now(), createdAt = createdAt ?? clock.now(),
description = description ?? ''; description = description ?? '',
icon = icon ?? '';
@override @override
String toString() { String toString() {
@@ -49,4 +50,3 @@ class Game {
'icon': icon, 'icon': icon,
}; };
} }

View File

@@ -27,8 +27,8 @@ class Match {
String? notes, String? notes,
this.winner, this.winner,
}) : id = id ?? const Uuid().v4(), }) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? clock.now(), createdAt = createdAt ?? clock.now(),
notes = notes ?? ''; notes = notes ?? '';
@override @override
String toString() { String toString() {
@@ -38,14 +38,21 @@ class Match {
/// Creates a Match instance from a JSON object (ID references format). /// Creates a Match instance from a JSON object (ID references format).
/// Related objects are reconstructed from IDs by the DataTransferService. /// Related objects are reconstructed from IDs by the DataTransferService.
Match.fromJson(Map<String, dynamic> json) Match.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
endedAt = json['endedAt'] != null ? DateTime.parse(json['endedAt']) : null, endedAt = json['endedAt'] != null
name = json['name'], ? DateTime.parse(json['endedAt'])
game = Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), // Populated during import via DataTransferService : null,
group = null, // Populated during import via DataTransferService name = json['name'],
players = [], // Populated during import via DataTransferService game = Game(
notes = json['notes'] ?? ''; name: '',
ruleset: Ruleset.singleWinner,
description: '',
color: GameColor.blue,
), // 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). /// Converts the Match instance to a JSON object using normalized format (ID references only).
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {

View File

@@ -42,7 +42,12 @@ class _HomeViewState extends State<HomeView> {
2, 2,
Match( Match(
name: 'Skeleton 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,
),
group: Group( group: Group(
name: 'Skeleton Group', name: 'Skeleton Group',
description: '', description: '',
@@ -104,7 +109,9 @@ class _HomeViewState extends State<HomeView> {
if (recentMatches.isNotEmpty) if (recentMatches.isNotEmpty)
for (Match match in recentMatches) for (Match match in recentMatches)
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0), padding: const EdgeInsets.symmetric(
vertical: 6.0,
),
child: MatchTile( child: MatchTile(
compact: true, compact: true,
width: constraints.maxWidth * 0.9, width: constraints.maxWidth * 0.9,
@@ -113,7 +120,8 @@ class _HomeViewState extends State<HomeView> {
await Navigator.of(context).push( await Navigator.of(context).push(
adaptivePageRoute( adaptivePageRoute(
fullscreenDialog: true, fullscreenDialog: true,
builder: (context) => MatchResultView(match: match), builder: (context) =>
MatchResultView(match: match),
), ),
); );
await updatedWinnerInRecentMatches(match.id); await updatedWinnerInRecentMatches(match.id);
@@ -121,7 +129,10 @@ class _HomeViewState extends State<HomeView> {
), ),
) )
else else
Center(heightFactor: 5, child: Text(loc.no_recent_matches_available)), Center(
heightFactor: 5,
child: Text(loc.no_recent_matches_available),
),
], ],
), ),
), ),
@@ -137,22 +148,40 @@ class _HomeViewState extends State<HomeView> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
QuickCreateButton(text: 'Category 1', onPressed: () {}), QuickCreateButton(
QuickCreateButton(text: 'Category 2', onPressed: () {}), text: 'Category 1',
onPressed: () {},
),
QuickCreateButton(
text: 'Category 2',
onPressed: () {},
),
], ],
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
QuickCreateButton(text: 'Category 3', onPressed: () {}), QuickCreateButton(
QuickCreateButton(text: 'Category 4', onPressed: () {}), text: 'Category 3',
onPressed: () {},
),
QuickCreateButton(
text: 'Category 4',
onPressed: () {},
),
], ],
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
QuickCreateButton(text: 'Category 5', onPressed: () {}), QuickCreateButton(
QuickCreateButton(text: 'Category 6', onPressed: () {}), text: 'Category 5',
onPressed: () {},
),
QuickCreateButton(
text: 'Category 6',
onPressed: () {},
),
], ],
), ),
], ],
@@ -181,9 +210,11 @@ class _HomeViewState extends State<HomeView> {
matchCount = results[0] as int; matchCount = results[0] as int;
groupCount = results[1] as int; groupCount = results[1] as int;
loadedRecentMatches = results[2] as List<Match>; loadedRecentMatches = results[2] as List<Match>;
recentMatches = (loadedRecentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) recentMatches =
.take(2) (loadedRecentMatches
.toList(); ..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
.take(2)
.toList();
if (mounted) { if (mounted) {
setState(() { setState(() {
isLoading = false; isLoading = false;

View File

@@ -261,7 +261,6 @@ class _CreateGameViewState extends State<CreateGameView> {
description: _descriptionController.text.trim(), description: _descriptionController.text.trim(),
ruleset: selectedRuleset!, ruleset: selectedRuleset!,
color: selectedColor!, color: selectedColor!,
icon: '',
); );
if (isEditing) { if (isEditing) {
await handleGameUpdate(newGame); await handleGameUpdate(newGame);

View File

@@ -44,7 +44,6 @@ class _MatchViewState extends State<MatchView> {
ruleset: Ruleset.singleWinner, ruleset: Ruleset.singleWinner,
description: '', description: '',
color: GameColor.blue, color: GameColor.blue,
icon: '',
), ),
group: Group( group: Group(
name: 'Group name', name: 'Group name',

View File

@@ -40,33 +40,39 @@ class DataTransferService {
'players': players.map((p) => p.toJson()).toList(), 'players': players.map((p) => p.toJson()).toList(),
'games': games.map((g) => g.toJson()).toList(), 'games': games.map((g) => g.toJson()).toList(),
'groups': groups 'groups': groups
.map((g) => { .map(
'id': g.id, (g) => {
'name': g.name, 'id': g.id,
'description': g.description, 'name': g.name,
'createdAt': g.createdAt.toIso8601String(), 'description': g.description,
'memberIds': (g.members).map((m) => m.id).toList(), 'createdAt': g.createdAt.toIso8601String(),
}) 'memberIds': (g.members).map((m) => m.id).toList(),
},
)
.toList(), .toList(),
'teams': teams 'teams': teams
.map((t) => { .map(
'id': t.id, (t) => {
'name': t.name, 'id': t.id,
'createdAt': t.createdAt.toIso8601String(), 'name': t.name,
'memberIds': (t.members).map((m) => m.id).toList(), 'createdAt': t.createdAt.toIso8601String(),
}) 'memberIds': (t.members).map((m) => m.id).toList(),
},
)
.toList(), .toList(),
'matches': matches 'matches': matches
.map((m) => { .map(
'id': m.id, (m) => {
'name': m.name, 'id': m.id,
'createdAt': m.createdAt.toIso8601String(), 'name': m.name,
'endedAt': m.endedAt?.toIso8601String(), 'createdAt': m.createdAt.toIso8601String(),
'gameId': m.game.id, 'endedAt': m.endedAt?.toIso8601String(),
'groupId': m.group?.id, 'gameId': m.game.id,
'playerIds': m.players.map((p) => p.id).toList(), 'groupId': m.group?.id,
'notes': m.notes, 'playerIds': m.players.map((p) => p.id).toList(),
}) 'notes': m.notes,
},
)
.toList(), .toList(),
}; };
@@ -79,9 +85,9 @@ class DataTransferService {
/// [jsonString] The JSON string to be exported. /// [jsonString] The JSON string to be exported.
/// [fileName] The desired name for the exported file (without extension). /// [fileName] The desired name for the exported file (without extension).
static Future<ExportResult> exportData( static Future<ExportResult> exportData(
String jsonString, String jsonString,
String fileName String fileName,
) async { ) async {
try { try {
final bytes = Uint8List.fromList(utf8.encode(jsonString)); final bytes = Uint8List.fromList(utf8.encode(jsonString));
final path = await FilePicker.platform.saveFile( final path = await FilePicker.platform.saveFile(
@@ -94,7 +100,6 @@ class DataTransferService {
} else { } else {
return ExportResult.success; return ExportResult.success;
} }
} catch (e, stack) { } catch (e, stack) {
print('[exportData] $e'); print('[exportData] $e');
print(stack); print(stack);
@@ -122,13 +127,19 @@ class DataTransferService {
final isValid = await _validateJsonSchema(jsonString); final isValid = await _validateJsonSchema(jsonString);
if (!isValid) return ImportResult.invalidSchema; if (!isValid) return ImportResult.invalidSchema;
final Map<String, dynamic> decoded = json.decode(jsonString) as Map<String, dynamic>; final Map<String, dynamic> decoded =
json.decode(jsonString) as Map<String, dynamic>;
final List<dynamic> playersJson = (decoded['players'] as List<dynamic>?) ?? []; final List<dynamic> playersJson =
final List<dynamic> gamesJson = (decoded['games'] as List<dynamic>?) ?? []; (decoded['players'] as List<dynamic>?) ?? [];
final List<dynamic> groupsJson = (decoded['groups'] as List<dynamic>?) ?? []; final List<dynamic> gamesJson =
final List<dynamic> teamsJson = (decoded['teams'] as List<dynamic>?) ?? []; (decoded['games'] as List<dynamic>?) ?? [];
final List<dynamic> matchesJson = (decoded['matches'] as List<dynamic>?) ?? []; final List<dynamic> groupsJson =
(decoded['groups'] as List<dynamic>?) ?? [];
final List<dynamic> teamsJson =
(decoded['teams'] as List<dynamic>?) ?? [];
final List<dynamic> matchesJson =
(decoded['matches'] as List<dynamic>?) ?? [];
// Import Players // Import Players
final List<Player> importedPlayers = playersJson final List<Player> importedPlayers = playersJson
@@ -151,7 +162,8 @@ class DataTransferService {
// Import Groups // Import Groups
final List<Group> importedGroups = groupsJson.map((g) { final List<Group> importedGroups = groupsJson.map((g) {
final map = g as Map<String, dynamic>; final map = g as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? []).cast<String>(); final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
.cast<String>();
final members = memberIds final members = memberIds
.map((id) => playerById[id]) .map((id) => playerById[id])
@@ -174,7 +186,8 @@ class DataTransferService {
// Import Teams // Import Teams
final List<Team> importedTeams = teamsJson.map((t) { final List<Team> importedTeams = teamsJson.map((t) {
final map = t as Map<String, dynamic>; final map = t as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? []).cast<String>(); final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
.cast<String>();
final members = memberIds final members = memberIds
.map((id) => playerById[id]) .map((id) => playerById[id])
@@ -195,8 +208,11 @@ class DataTransferService {
final String gameId = map['gameId'] as String; final String gameId = map['gameId'] as String;
final String? groupId = map['groupId'] as String?; final String? groupId = map['groupId'] as String?;
final List<String> playerIds = (map['playerIds'] as List<dynamic>? ?? []).cast<String>(); final List<String> playerIds =
final DateTime? endedAt = map['endedAt'] != null ? DateTime.parse(map['endedAt'] as String) : null; (map['playerIds'] as List<dynamic>? ?? []).cast<String>();
final DateTime? endedAt = map['endedAt'] != null
? DateTime.parse(map['endedAt'] as String)
: null;
final game = gameById[gameId]; final game = gameById[gameId];
final group = (groupId == null) ? null : groupById[groupId]; final group = (groupId == null) ? null : groupById[groupId];
@@ -208,7 +224,14 @@ class DataTransferService {
return Match( return Match(
id: map['id'] as String, id: map['id'] as String,
name: map['name'] as String, name: map['name'] as String,
game: game ?? Game(name: 'Unknown', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), game:
game ??
Game(
name: 'Unknown',
ruleset: Ruleset.singleWinner,
description: '',
color: GameColor.blue,
),
group: group, group: group,
players: players, players: players,
createdAt: DateTime.parse(map['createdAt'] as String), createdAt: DateTime.parse(map['createdAt'] as String),
@@ -266,4 +289,4 @@ class DataTransferService {
return false; return false;
} }
} }
} }

View File

@@ -56,7 +56,6 @@ void main() {
ruleset: Ruleset.singleWinner, ruleset: Ruleset.singleWinner,
description: 'A test game', description: 'A test game',
color: GameColor.blue, color: GameColor.blue,
icon: '',
); );
testMatch1 = Match( testMatch1 = Match(
name: 'First Test Match', name: 'First Test Match',

View File

@@ -2,12 +2,12 @@ import 'package:clock/clock.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.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/db/database.dart';
import 'package:tallee/data/dto/game.dart'; import 'package:tallee/data/dto/game.dart';
import 'package:tallee/data/dto/match.dart'; import 'package:tallee/data/dto/match.dart';
import 'package:tallee/data/dto/player.dart'; import 'package:tallee/data/dto/player.dart';
import 'package:tallee/data/dto/team.dart'; import 'package:tallee/data/dto/team.dart';
import 'package:tallee/core/enums.dart';
void main() { void main() {
late AppDatabase database; late AppDatabase database;
@@ -37,20 +37,21 @@ void main() {
testPlayer2 = Player(name: 'Bob', description: ''); testPlayer2 = Player(name: 'Bob', description: '');
testPlayer3 = Player(name: 'Charlie', description: ''); testPlayer3 = Player(name: 'Charlie', description: '');
testPlayer4 = Player(name: 'Diana', description: ''); testPlayer4 = Player(name: 'Diana', description: '');
testTeam1 = Team( testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]);
name: 'Team Alpha', testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]);
members: [testPlayer1, testPlayer2], testTeam3 = Team(name: 'Team Gamma', members: [testPlayer1, testPlayer3]);
testGame1 = Game(
name: 'Game 1',
ruleset: Ruleset.singleWinner,
description: 'Test game 1',
color: GameColor.blue,
); );
testTeam2 = Team( testGame2 = Game(
name: 'Team Beta', name: 'Game 2',
members: [testPlayer3, testPlayer4], ruleset: Ruleset.highestScore,
description: 'Test game 2',
color: GameColor.red,
); );
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( await database.playerDao.addPlayersAsList(
@@ -65,7 +66,6 @@ void main() {
}); });
group('Team Tests', () { group('Team Tests', () {
// Verifies that a single team can be added and retrieved with all fields intact. // Verifies that a single team can be added and retrieved with all fields intact.
test('Adding and fetching a single team works correctly', () async { test('Adding and fetching a single team works correctly', () async {
final added = await database.teamDao.addTeam(team: testTeam1); final added = await database.teamDao.addTeam(team: testTeam1);
@@ -285,10 +285,7 @@ void main() {
test('Updating team name to empty string works', () async { test('Updating team name to empty string works', () async {
await database.teamDao.addTeam(team: testTeam1); await database.teamDao.addTeam(team: testTeam1);
await database.teamDao.updateTeamName( await database.teamDao.updateTeamName(teamId: testTeam1.id, newName: '');
teamId: testTeam1.id,
newName: '',
);
final updatedTeam = await database.teamDao.getTeamById( final updatedTeam = await database.teamDao.getTeamById(
teamId: testTeam1.id, teamId: testTeam1.id,
@@ -350,9 +347,7 @@ void main() {
await database.matchDao.addMatch(match: match2); await database.matchDao.addMatch(match: match2);
// Add teams to database // Add teams to database
await database.teamDao.addTeamsAsList( await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam3]);
teams: [testTeam1, testTeam3],
);
// Associate players with teams through match1 // Associate players with teams through match1
// testTeam1: player1, player2 // testTeam1: player1, player2
@@ -420,10 +415,11 @@ void main() {
final allTeams = await database.teamDao.getAllTeams(); final allTeams = await database.teamDao.getAllTeams();
expect(allTeams.length, 3); expect(allTeams.length, 3);
expect( expect(allTeams.map((t) => t.id).toSet(), {
allTeams.map((t) => t.id).toSet(), testTeam1.id,
{testTeam1.id, testTeam2.id, testTeam3.id}, testTeam2.id,
); testTeam3.id,
});
}); });
// Verifies that teamExists returns false for deleted teams. // Verifies that teamExists returns false for deleted teams.
@@ -462,9 +458,7 @@ void main() {
// Verifies that addTeam after deleteAllTeams works correctly. // Verifies that addTeam after deleteAllTeams works correctly.
test('Adding team after deleteAllTeams works correctly', () async { test('Adding team after deleteAllTeams works correctly', () async {
await database.teamDao.addTeamsAsList( await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam2]);
teams: [testTeam1, testTeam2],
);
expect(await database.teamDao.getTeamCount(), 2); expect(await database.teamDao.getTeamCount(), 2);
await database.teamDao.deleteAllTeams(); await database.teamDao.deleteAllTeams();
@@ -524,4 +518,4 @@ void main() {
expect(fetchedTeam.createdAt, testTeam1.createdAt); expect(fetchedTeam.createdAt, testTeam1.createdAt);
}); });
}); });
} }

View File

@@ -44,7 +44,6 @@ void main() {
ruleset: Ruleset.highestScore, ruleset: Ruleset.highestScore,
description: 'A board game about real estate', description: 'A board game about real estate',
color: GameColor.orange, color: GameColor.orange,
icon: '',
); );
}); });
}); });
@@ -54,7 +53,6 @@ void main() {
}); });
group('Game Tests', () { group('Game Tests', () {
// Verifies that getAllGames returns an empty list when the database has no games. // Verifies that getAllGames returns an empty list when the database has no games.
test('getAllGames returns empty list when no games exist', () async { test('getAllGames returns empty list when no games exist', () async {
final allGames = await database.gameDao.getAllGames(); final allGames = await database.gameDao.getAllGames();
@@ -106,7 +104,7 @@ void main() {
// Verifies that getGameById throws a StateError when the game doesn't exist. // Verifies that getGameById throws a StateError when the game doesn't exist.
test('getGameById throws exception for non-existent game', () async { test('getGameById throws exception for non-existent game', () async {
expect( expect(
() => database.gameDao.getGameById(gameId: 'non-existent-id'), () => database.gameDao.getGameById(gameId: 'non-existent-id'),
throwsA(isA<StateError>()), throwsA(isA<StateError>()),
); );
}); });
@@ -134,7 +132,12 @@ void main() {
// Verifies that a game with empty optional fields can be added and retrieved. // Verifies that a game with empty optional fields can be added and retrieved.
test('addGame handles game with null optional fields', () async { 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,
);
final result = await database.gameDao.addGame(game: gameWithNulls); final result = await database.gameDao.addGame(game: gameWithNulls);
expect(result, true); expect(result, true);
@@ -419,9 +422,7 @@ void main() {
// Verifies that getGameCount updates correctly after deleting a game. // Verifies that getGameCount updates correctly after deleting a game.
test('getGameCount updates correctly after deletion', () async { test('getGameCount updates correctly after deletion', () async {
await database.gameDao.addGamesAsList( await database.gameDao.addGamesAsList(games: [testGame1, testGame2]);
games: [testGame1, testGame2],
);
final countBefore = await database.gameDao.getGameCount(); final countBefore = await database.gameDao.getGameCount();
expect(countBefore, 2); expect(countBefore, 2);
@@ -461,7 +462,6 @@ void main() {
ruleset: Ruleset.multipleWinners, ruleset: Ruleset.multipleWinners,
description: 'Description with émojis 🎮🎲', description: 'Description with émojis 🎮🎲',
color: GameColor.purple, color: GameColor.purple,
icon: '',
); );
await database.gameDao.addGame(game: specialGame); await database.gameDao.addGame(game: specialGame);
@@ -478,7 +478,6 @@ void main() {
name: '', name: '',
ruleset: Ruleset.singleWinner, ruleset: Ruleset.singleWinner,
description: '', description: '',
icon: '',
color: GameColor.red, color: GameColor.red,
); );
await database.gameDao.addGame(game: emptyGame); await database.gameDao.addGame(game: emptyGame);
@@ -500,7 +499,6 @@ void main() {
description: longString, description: longString,
ruleset: Ruleset.multipleWinners, ruleset: Ruleset.multipleWinners,
color: GameColor.yellow, color: GameColor.yellow,
icon: '',
); );
await database.gameDao.addGame(game: longGame); await database.gameDao.addGame(game: longGame);

View File

@@ -48,7 +48,12 @@ void main() {
description: '', description: '',
members: [testPlayer1, testPlayer2, testPlayer3], 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,
);
testMatchOnlyGroup = Match( testMatchOnlyGroup = Match(
name: 'Test Match with Group', name: 'Test Match with Group',
game: testGame, game: testGame,
@@ -61,14 +66,8 @@ void main() {
players: [testPlayer4, testPlayer5, testPlayer6], players: [testPlayer4, testPlayer5, testPlayer6],
notes: '', notes: '',
); );
testTeam1 = Team( testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]);
name: 'Team Alpha', testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]);
members: [testPlayer1, testPlayer2],
);
testTeam2 = Team(
name: 'Team Beta',
members: [testPlayer3, testPlayer4],
);
}); });
await database.playerDao.addPlayersAsList( await database.playerDao.addPlayersAsList(
players: [ players: [
@@ -88,7 +87,6 @@ void main() {
}); });
group('Player-Match Tests', () { group('Player-Match Tests', () {
// Verifies that matchHasPlayers returns false initially and true after adding a player. // Verifies that matchHasPlayers returns false initially and true after adding a player.
test('Match has player works correctly', () async { test('Match has player works correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.matchDao.addMatch(match: testMatchOnlyGroup);
@@ -153,26 +151,23 @@ void main() {
); );
expect(result.players.length, testMatchOnlyPlayers.players.length - 1); expect(result.players.length, testMatchOnlyPlayers.players.length - 1);
final playerExists = result.players.any( final playerExists = result.players.any((p) => p.id == playerToRemove.id);
(p) => p.id == playerToRemove.id,
);
expect(playerExists, false); expect(playerExists, false);
}); });
// Verifies that getPlayersOfMatch returns all players of a match with correct data. // Verifies that getPlayersOfMatch returns all players of a match with correct data.
test('Retrieving players of a match works correctly', () async { test('Retrieving players of a match works correctly', () async {
await database.matchDao.addMatch(match: testMatchOnlyPlayers); await database.matchDao.addMatch(match: testMatchOnlyPlayers);
final players = await database.playerMatchDao.getPlayersOfMatch( final players =
matchId: testMatchOnlyPlayers.id, await database.playerMatchDao.getPlayersOfMatch(
) ?? []; matchId: testMatchOnlyPlayers.id,
) ??
[];
for (int i = 0; i < players.length; i++) { for (int i = 0; i < players.length; i++) {
expect(players[i].id, testMatchOnlyPlayers.players[i].id); expect(players[i].id, testMatchOnlyPlayers.players[i].id);
expect(players[i].name, testMatchOnlyPlayers.players[i].name); expect(players[i].name, testMatchOnlyPlayers.players[i].name);
expect( expect(players[i].createdAt, testMatchOnlyPlayers.players[i].createdAt);
players[i].createdAt,
testMatchOnlyPlayers.players[i].createdAt,
);
} }
}); });
@@ -223,10 +218,20 @@ void main() {
// Verifies that the same player can be added to multiple different matches. // Verifies that the same player can be added to multiple different matches.
test( test(
'Adding the same player to separate matches works correctly', 'Adding the same player to separate matches works correctly',
() async { () async {
final playersList = [testPlayer1, testPlayer2, testPlayer3]; final playersList = [testPlayer1, testPlayer2, testPlayer3];
final match1 = Match(name: 'Match 1', game: testGame, players: playersList, notes: ''); final match1 = Match(
final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); name: 'Match 1',
game: testGame,
players: playersList,
notes: '',
);
final match2 = Match(
name: 'Match 2',
game: testGame,
players: playersList,
notes: '',
);
await Future.wait([ await Future.wait([
database.matchDao.addMatch(match: match1), database.matchDao.addMatch(match: match1),
@@ -299,16 +304,19 @@ void main() {
}); });
// Verifies that getPlayerScore returns null for non-existent player-match combination. // Verifies that getPlayerScore returns null for non-existent player-match combination.
test('getPlayerScore returns null for non-existent player in match', () async { test(
await database.matchDao.addMatch(match: testMatchOnlyGroup); 'getPlayerScore returns null for non-existent player in match',
() async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
final score = await database.playerMatchDao.getPlayerScore( final score = await database.playerMatchDao.getPlayerScore(
matchId: testMatchOnlyGroup.id, matchId: testMatchOnlyGroup.id,
playerId: 'non-existent-player-id', playerId: 'non-existent-player-id',
); );
expect(score, isNull); expect(score, isNull);
}); },
);
// Verifies that updatePlayerScore updates the score correctly. // Verifies that updatePlayerScore updates the score correctly.
test('updatePlayerScore updates score correctly', () async { test('updatePlayerScore updates score correctly', () async {
@@ -331,17 +339,20 @@ void main() {
}); });
// Verifies that updatePlayerScore returns false for non-existent player-match. // Verifies that updatePlayerScore returns false for non-existent player-match.
test('updatePlayerScore returns false for non-existent player-match', () async { test(
await database.matchDao.addMatch(match: testMatchOnlyGroup); 'updatePlayerScore returns false for non-existent player-match',
() async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
final updated = await database.playerMatchDao.updatePlayerScore( final updated = await database.playerMatchDao.updatePlayerScore(
matchId: testMatchOnlyGroup.id, matchId: testMatchOnlyGroup.id,
playerId: 'non-existent-player-id', playerId: 'non-existent-player-id',
newScore: 50, newScore: 50,
); );
expect(updated, false); expect(updated, false);
}); },
);
// Verifies that adding a player with teamId works correctly. // Verifies that adding a player with teamId works correctly.
test('Adding player with teamId works correctly', () async { test('Adding player with teamId works correctly', () async {
@@ -431,17 +442,20 @@ void main() {
}); });
// Verifies that updatePlayerTeam returns false for non-existent player-match. // Verifies that updatePlayerTeam returns false for non-existent player-match.
test('updatePlayerTeam returns false for non-existent player-match', () async { test(
await database.matchDao.addMatch(match: testMatchOnlyGroup); 'updatePlayerTeam returns false for non-existent player-match',
() async {
await database.matchDao.addMatch(match: testMatchOnlyGroup);
final updated = await database.playerMatchDao.updatePlayerTeam( final updated = await database.playerMatchDao.updatePlayerTeam(
matchId: testMatchOnlyGroup.id, matchId: testMatchOnlyGroup.id,
playerId: 'non-existent-player-id', playerId: 'non-existent-player-id',
teamId: testTeam1.id, teamId: testTeam1.id,
); );
expect(updated, false); expect(updated, false);
}); },
);
// Verifies that getPlayersInTeam returns empty list for non-existent team. // Verifies that getPlayersInTeam returns empty list for non-existent team.
test('getPlayersInTeam returns empty list for non-existent team', () async { test('getPlayersInTeam returns empty list for non-existent team', () async {
@@ -483,16 +497,19 @@ void main() {
}); });
// Verifies that removePlayerFromMatch returns false for non-existent player. // Verifies that removePlayerFromMatch returns false for non-existent player.
test('removePlayerFromMatch returns false for non-existent player', () async { test(
await database.matchDao.addMatch(match: testMatchOnlyPlayers); 'removePlayerFromMatch returns false for non-existent player',
() async {
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
final removed = await database.playerMatchDao.removePlayerFromMatch( final removed = await database.playerMatchDao.removePlayerFromMatch(
playerId: 'non-existent-player-id', playerId: 'non-existent-player-id',
matchId: testMatchOnlyPlayers.id, matchId: testMatchOnlyPlayers.id,
); );
expect(removed, false); expect(removed, false);
}); },
);
// Verifies that adding the same player twice to the same match is ignored. // Verifies that adding the same player twice to the same match is ignored.
test('Adding same player twice to same match is ignored', () async { test('Adding same player twice to same match is ignored', () async {
@@ -528,27 +545,30 @@ void main() {
}); });
// Verifies that updatePlayersFromMatch with empty list removes all players. // Verifies that updatePlayersFromMatch with empty list removes all players.
test('updatePlayersFromMatch with empty list removes all players', () async { test(
await database.matchDao.addMatch(match: testMatchOnlyPlayers); 'updatePlayersFromMatch with empty list removes all players',
() async {
await database.matchDao.addMatch(match: testMatchOnlyPlayers);
// Verify players exist initially // Verify players exist initially
var players = await database.playerMatchDao.getPlayersOfMatch( var players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatchOnlyPlayers.id, matchId: testMatchOnlyPlayers.id,
); );
expect(players?.length, 3); expect(players?.length, 3);
// Update with empty list // Update with empty list
await database.playerMatchDao.updatePlayersFromMatch( await database.playerMatchDao.updatePlayersFromMatch(
matchId: testMatchOnlyPlayers.id, matchId: testMatchOnlyPlayers.id,
newPlayer: [], newPlayer: [],
); );
// Verify all players are removed // Verify all players are removed
players = await database.playerMatchDao.getPlayersOfMatch( players = await database.playerMatchDao.getPlayersOfMatch(
matchId: testMatchOnlyPlayers.id, matchId: testMatchOnlyPlayers.id,
); );
expect(players, isNull); expect(players, isNull);
}); },
);
// Verifies that updatePlayersFromMatch with same players makes no changes. // Verifies that updatePlayersFromMatch with same players makes no changes.
test('updatePlayersFromMatch with same players makes no changes', () async { test('updatePlayersFromMatch with same players makes no changes', () async {
@@ -702,16 +722,19 @@ void main() {
}); });
// Verifies that getPlayersInTeam returns empty list for non-existent match. // Verifies that getPlayersInTeam returns empty list for non-existent match.
test('getPlayersInTeam returns empty list for non-existent match', () async { test(
await database.teamDao.addTeam(team: testTeam1); 'getPlayersInTeam returns empty list for non-existent match',
() async {
await database.teamDao.addTeam(team: testTeam1);
final players = await database.playerMatchDao.getPlayersInTeam( final players = await database.playerMatchDao.getPlayersInTeam(
matchId: 'non-existent-match-id', matchId: 'non-existent-match-id',
teamId: testTeam1.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. // Verifies that players in different teams within the same match are returned correctly.
test('Players in different teams within same match are separate', () async { test('Players in different teams within same match are separate', () async {
@@ -759,8 +782,18 @@ void main() {
// Verifies that removePlayerFromMatch does not affect other matches. // Verifies that removePlayerFromMatch does not affect other matches.
test('removePlayerFromMatch does not affect other matches', () async { test('removePlayerFromMatch does not affect other matches', () async {
final playersList = [testPlayer1, testPlayer2]; final playersList = [testPlayer1, testPlayer2];
final match1 = Match(name: 'Match 1', game: testGame, players: playersList, notes: ''); final match1 = Match(
final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); name: 'Match 1',
game: testGame,
players: playersList,
notes: '',
);
final match2 = Match(
name: 'Match 2',
game: testGame,
players: playersList,
notes: '',
);
await Future.wait([ await Future.wait([
database.matchDao.addMatch(match: match1), database.matchDao.addMatch(match: match1),
@@ -792,8 +825,18 @@ void main() {
// Verifies that updating scores for players in different matches are independent. // Verifies that updating scores for players in different matches are independent.
test('Player scores are independent across matches', () async { test('Player scores are independent across matches', () async {
final playersList = [testPlayer1]; final playersList = [testPlayer1];
final match1 = Match(name: 'Match 1', game: testGame, players: playersList, notes: ''); final match1 = Match(
final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); name: 'Match 1',
game: testGame,
players: playersList,
notes: '',
);
final match2 = Match(
name: 'Match 2',
game: testGame,
players: playersList,
notes: '',
);
await Future.wait([ await Future.wait([
database.matchDao.addMatch(match: match1), database.matchDao.addMatch(match: match1),
@@ -829,16 +872,19 @@ void main() {
}); });
// Verifies that updatePlayersFromMatch on non-existent match fails with constraint error. // Verifies that updatePlayersFromMatch on non-existent match fails with constraint error.
test('updatePlayersFromMatch on non-existent match fails with foreign key constraint', () async { test(
// Should throw due to foreign key constraint - match doesn't exist 'updatePlayersFromMatch on non-existent match fails with foreign key constraint',
await expectLater( () async {
database.playerMatchDao.updatePlayersFromMatch( // Should throw due to foreign key constraint - match doesn't exist
matchId: 'non-existent-match-id', await expectLater(
newPlayer: [testPlayer1, testPlayer2], database.playerMatchDao.updatePlayersFromMatch(
), matchId: 'non-existent-match-id',
throwsA(anything), newPlayer: [testPlayer1, testPlayer2],
); ),
}); throwsA(anything),
);
},
);
// Verifies that a player can be in a match without being assigned to a team. // 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 { test('Player can exist in match without team assignment', () async {

View File

@@ -2,11 +2,11 @@ import 'package:clock/clock.dart';
import 'package:drift/drift.dart' hide isNull, isNotNull; import 'package:drift/drift.dart' hide isNull, isNotNull;
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.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/db/database.dart';
import 'package:tallee/data/dto/game.dart'; import 'package:tallee/data/dto/game.dart';
import 'package:tallee/data/dto/match.dart'; import 'package:tallee/data/dto/match.dart';
import 'package:tallee/data/dto/player.dart'; import 'package:tallee/data/dto/player.dart';
import 'package:tallee/core/enums.dart';
void main() { void main() {
late AppDatabase database; late AppDatabase database;
@@ -32,7 +32,12 @@ void main() {
testPlayer1 = Player(name: 'Alice', description: ''); testPlayer1 = Player(name: 'Alice', description: '');
testPlayer2 = Player(name: 'Bob', description: ''); testPlayer2 = Player(name: 'Bob', description: '');
testPlayer3 = Player(name: 'Charlie', 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,
);
testMatch1 = Match( testMatch1 = Match(
name: 'Test Match 1', name: 'Test Match 1',
game: testGame, game: testGame,
@@ -60,7 +65,6 @@ void main() {
}); });
group('Score Tests', () { group('Score Tests', () {
// Verifies that a score can be added and retrieved with all fields intact. // Verifies that a score can be added and retrieved with all fields intact.
test('Adding and fetching a score works correctly', () async { test('Adding and fetching a score works correctly', () async {
await database.scoreDao.addScore( await database.scoreDao.addScore(
@@ -431,13 +435,16 @@ void main() {
}); });
// Verifies that getScoresForMatch returns empty list for match with no scores. // Verifies that getScoresForMatch returns empty list for match with no scores.
test('Getting scores for match with no scores returns empty list', () async { test(
final scores = await database.scoreDao.getScoresForMatch( 'Getting scores for match with no scores returns empty list',
matchId: testMatch1.id, () async {
); final scores = await database.scoreDao.getScoresForMatch(
matchId: testMatch1.id,
);
expect(scores.isEmpty, true); expect(scores.isEmpty, true);
}); },
);
// Verifies that getPlayerScoresInMatch returns empty list when player has no scores. // Verifies that getPlayerScoresInMatch returns empty list when player has no scores.
test('Getting player scores with no scores returns empty list', () async { test('Getting player scores with no scores returns empty list', () async {
@@ -666,46 +673,58 @@ void main() {
}); });
// Verifies that updating one player's score doesn't affect another player's score in same round. // 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 { test(
await database.scoreDao.addScore( 'Updating one player score does not affect other players in same round',
playerId: testPlayer1.id, () async {
matchId: testMatch1.id, await database.scoreDao.addScore(
roundNumber: 1, playerId: testPlayer1.id,
score: 10, matchId: testMatch1.id,
change: 10, roundNumber: 1,
); score: 10,
await database.scoreDao.addScore( change: 10,
playerId: testPlayer2.id, );
matchId: testMatch1.id, await database.scoreDao.addScore(
roundNumber: 1, playerId: testPlayer2.id,
score: 20, matchId: testMatch1.id,
change: 20, roundNumber: 1,
); score: 20,
await database.scoreDao.addScore( change: 20,
playerId: testPlayer3.id, );
matchId: testMatch1.id, await database.scoreDao.addScore(
roundNumber: 1, playerId: testPlayer3.id,
score: 30, matchId: testMatch1.id,
change: 30, roundNumber: 1,
); score: 30,
change: 30,
);
await database.scoreDao.updateScore( await database.scoreDao.updateScore(
playerId: testPlayer2.id, playerId: testPlayer2.id,
matchId: testMatch1.id, matchId: testMatch1.id,
roundNumber: 1, roundNumber: 1,
newScore: 99, newScore: 99,
newChange: 89, newChange: 89,
); );
final scores = await database.scoreDao.getScoresForMatch( final scores = await database.scoreDao.getScoresForMatch(
matchId: testMatch1.id, matchId: testMatch1.id,
); );
expect(scores.length, 3); expect(scores.length, 3);
expect(scores.where((s) => s.playerId == testPlayer1.id).first.score, 10); expect(
expect(scores.where((s) => s.playerId == testPlayer2.id).first.score, 99); scores.where((s) => s.playerId == testPlayer1.id).first.score,
expect(scores.where((s) => s.playerId == testPlayer3.id).first.score, 30); 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. // Verifies that deleting a player's scores only affects that specific player.
test('Deleting player scores only affects target player', () async { test('Deleting player scores only affects target player', () async {
@@ -724,9 +743,7 @@ void main() {
change: 20, change: 20,
); );
await database.scoreDao.deleteScoresForPlayer( await database.scoreDao.deleteScoresForPlayer(playerId: testPlayer1.id);
playerId: testPlayer1.id,
);
final match1Scores = await database.scoreDao.getScoresForMatch( final match1Scores = await database.scoreDao.getScoresForMatch(
matchId: testMatch1.id, matchId: testMatch1.id,