3 Commits

Author SHA1 Message Date
c43b7b478c Updated comments
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 44s
Pull Request Pipeline / lint (pull_request) Successful in 46s
2026-04-13 22:47:29 +02:00
c9188c222a Added edge cases for games, groups, teams and matches 2026-04-13 22:16:34 +02:00
fcca74cea5 Refactoring 2026-04-13 22:16:12 +02:00
5 changed files with 118 additions and 46 deletions

View File

@@ -24,16 +24,17 @@ class Group {
return 'Group{id: $id, name: $name, description: $description, members: $members}'; return 'Group{id: $id, name: $name, description: $description, members: $members}';
} }
/// Creates a Group instance from a JSON object (memberIds format). /// Creates a Group instance from a JSON object where the related [Player]
/// Player objects are reconstructed from memberIds by the DataTransferService. /// objects are represented by their IDs.
Group.fromJson(Map<String, dynamic> json) Group.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
name = json['name'], name = json['name'],
description = json['description'], description = json['description'],
members = []; // Populated during import via DataTransferService members = [];
/// Converts the Group instance to a JSON object using normalized format (memberIds only). /// Converts the Group instance to a JSON object. Related [Player] objects are
/// represented by their IDs.
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),

View File

@@ -38,8 +38,9 @@ class Match {
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, winner: $winner}'; 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). /// Creates a Match instance from a JSON object where related objects are
/// Related objects are reconstructed from IDs by the DataTransferService. /// represented by their IDs. Therefore, the game, group, and players are not
/// fully constructed here.
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']),
@@ -53,13 +54,15 @@ class Match {
description: '', description: '',
color: GameColor.blue, color: GameColor.blue,
icon: '', icon: '',
), // Populated during import via DataTransferService ),
group = null, // Populated during import via DataTransferService group = null,
players = [], // Populated during import via DataTransferService players = [],
scores = json['scores'], scores = json['scores'],
notes = json['notes'] ?? ''; 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. Related objects are
/// represented by their IDs, so the game, group, and players are not fully
/// serialized here.
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),

View File

@@ -29,7 +29,8 @@ class Team {
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
members = []; // Populated during import via DataTransferService members = []; // Populated during import via DataTransferService
/// Converts the Team instance to a JSON object using normalized format (memberIds only). /// Converts the Team instance to a JSON object. Related objects are
/// represented by their IDs.
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'name': name, 'name': name,

View File

@@ -18,6 +18,7 @@ class DataTransferService {
/// Deletes all data from the database. /// Deletes all data from the database.
static Future<void> deleteAllData(BuildContext context) async { static Future<void> deleteAllData(BuildContext context) async {
final db = Provider.of<AppDatabase>(context, listen: false); final db = Provider.of<AppDatabase>(context, listen: false);
await db.matchDao.deleteAllMatches(); await db.matchDao.deleteAllMatches();
await db.teamDao.deleteAllTeams(); await db.teamDao.deleteAllTeams();
await db.groupDao.deleteAllGroups(); await db.groupDao.deleteAllGroups();
@@ -96,8 +97,8 @@ class DataTransferService {
/// Exports the given JSON string to a file with the specified name. /// Exports the given JSON string to a file with the specified name.
/// Returns an [ExportResult] indicating the outcome. /// Returns an [ExportResult] indicating the outcome.
/// ///
/// [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,
@@ -163,21 +164,22 @@ class DataTransferService {
@visibleForTesting @visibleForTesting
static Future<void> importDataToDatabase( static Future<void> importDataToDatabase(
AppDatabase db, AppDatabase db,
Map<String, dynamic> decoded, Map<String, dynamic> decodedJson,
) async { ) async {
final importedPlayers = parsePlayersFromJson(decoded); // Fetch all entities first to create lookup maps for relationships
final importedPlayers = parsePlayersFromJson(decodedJson);
final playerById = {for (final p in importedPlayers) p.id: p}; final playerById = {for (final p in importedPlayers) p.id: p};
final importedGames = parseGamesFromJson(decoded); final importedGames = parseGamesFromJson(decodedJson);
final gameById = {for (final g in importedGames) g.id: g}; final gameById = {for (final g in importedGames) g.id: g};
final importedGroups = parseGroupsFromJson(decoded, playerById); final importedGroups = parseGroupsFromJson(decodedJson, playerById);
final groupById = {for (final g in importedGroups) g.id: g}; final groupById = {for (final g in importedGroups) g.id: g};
final importedTeams = parseTeamsFromJson(decoded, playerById); final importedTeams = parseTeamsFromJson(decodedJson, playerById);
final importedMatches = parseMatchesFromJson( final importedMatches = parseMatchesFromJson(
decoded, decodedJson,
gameById, gameById,
groupById, groupById,
playerById, playerById,
@@ -190,31 +192,30 @@ class DataTransferService {
await db.matchDao.addMatchAsList(matches: importedMatches); await db.matchDao.addMatchAsList(matches: importedMatches);
} }
/// Parses players from JSON data. /* Parsing Methods */
@visibleForTesting @visibleForTesting
static List<Player> parsePlayersFromJson(Map<String, dynamic> decoded) { static List<Player> parsePlayersFromJson(Map<String, dynamic> decodedJson) {
final playersJson = (decoded['players'] as List<dynamic>?) ?? []; final playersJson = (decodedJson['players'] as List<dynamic>?) ?? [];
return playersJson return playersJson
.map((p) => Player.fromJson(p as Map<String, dynamic>)) .map((p) => Player.fromJson(p as Map<String, dynamic>))
.toList(); .toList();
} }
/// Parses games from JSON data.
@visibleForTesting @visibleForTesting
static List<Game> parseGamesFromJson(Map<String, dynamic> decoded) { static List<Game> parseGamesFromJson(Map<String, dynamic> decodedJson) {
final gamesJson = (decoded['games'] as List<dynamic>?) ?? []; final gamesJson = (decodedJson['games'] as List<dynamic>?) ?? [];
return gamesJson return gamesJson
.map((g) => Game.fromJson(g as Map<String, dynamic>)) .map((g) => Game.fromJson(g as Map<String, dynamic>))
.toList(); .toList();
} }
/// Parses groups from JSON data.
@visibleForTesting @visibleForTesting
static List<Group> parseGroupsFromJson( static List<Group> parseGroupsFromJson(
Map<String, dynamic> decoded, Map<String, dynamic> decodedJson,
Map<String, Player> playerById, Map<String, Player> playerById,
) { ) {
final groupsJson = (decoded['groups'] as List<dynamic>?) ?? []; final groupsJson = (decodedJson['groups'] as List<dynamic>?) ?? [];
return groupsJson.map((g) { return groupsJson.map((g) {
final map = g as Map<String, dynamic>; final map = g as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? []) final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
@@ -238,10 +239,10 @@ class DataTransferService {
/// Parses teams from JSON data. /// Parses teams from JSON data.
@visibleForTesting @visibleForTesting
static List<Team> parseTeamsFromJson( static List<Team> parseTeamsFromJson(
Map<String, dynamic> decoded, Map<String, dynamic> decodedJson,
Map<String, Player> playerById, Map<String, Player> playerById,
) { ) {
final teamsJson = (decoded['teams'] as List<dynamic>?) ?? []; final teamsJson = (decodedJson['teams'] as List<dynamic>?) ?? [];
return teamsJson.map((t) { return teamsJson.map((t) {
final map = t as Map<String, dynamic>; final map = t as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? []) final memberIds = (map['memberIds'] as List<dynamic>? ?? [])
@@ -264,46 +265,53 @@ class DataTransferService {
/// Parses matches from JSON data. /// Parses matches from JSON data.
@visibleForTesting @visibleForTesting
static List<Match> parseMatchesFromJson( static List<Match> parseMatchesFromJson(
Map<String, dynamic> decoded, Map<String, dynamic> decodedJson,
Map<String, Game> gameById, Map<String, Game> gamesMap,
Map<String, Group> groupById, Map<String, Group> groupsMap,
Map<String, Player> playerById, Map<String, Player> playersMap,
) { ) {
final matchesJson = (decoded['matches'] as List<dynamic>?) ?? []; final matchesJson = (decodedJson['matches'] as List<dynamic>?) ?? [];
return matchesJson.map((m) { return matchesJson.map((m) {
final map = m as Map<String, dynamic>; final map = m as Map<String, dynamic>;
// Extract attributes from json
final id = map['id'] as String;
final name = map['name'] as String;
final gameId = map['gameId'] as String; final gameId = map['gameId'] as String;
final groupId = map['groupId'] as String?; final groupId = map['groupId'] as String?;
final playerIds = (map['playerIds'] as List<dynamic>? ?? []) final createdAt = DateTime.parse(map['createdAt'] as String);
.cast<String>();
final endedAt = map['endedAt'] != null final endedAt = map['endedAt'] != null
? DateTime.parse(map['endedAt'] as String) ? DateTime.parse(map['endedAt'] as String)
: null; : null;
final notes = map['notes'] as String? ?? '';
final game = gameById[gameId] ?? createUnknownGame(); // Link attributes to objects
final group = groupId != null ? groupById[groupId] : null; final game = gamesMap[gameId] ?? getFallbackGame();
final group = groupId != null ? groupsMap[groupId] : null;
final playerIds = (map['playerIds'] as List<dynamic>? ?? [])
.cast<String>();
final players = playerIds final players = playerIds
.map((id) => playerById[id]) .map((id) => playersMap[id])
.whereType<Player>() .whereType<Player>()
.toList(); .toList();
return Match( return Match(
id: map['id'] as String, id: id,
name: map['name'] as String, name: name,
game: game, game: game,
group: group, group: group,
players: players, players: players,
createdAt: DateTime.parse(map['createdAt'] as String), createdAt: createdAt,
endedAt: endedAt, endedAt: endedAt,
notes: map['notes'] as String? ?? '', notes: notes,
); );
}).toList(); }).toList();
} }
/// Creates a fallback game when the referenced game is not found. /// Creates a fallback game when the referenced game is not found.
@visibleForTesting @visibleForTesting
static Game createUnknownGame() { static Game getFallbackGame() {
return Game( return Game(
name: 'Unknown', name: 'Unknown',
ruleset: Ruleset.singleWinner, ruleset: Ruleset.singleWinner,
@@ -320,7 +328,8 @@ class DataTransferService {
return null; return null;
} }
/// Validates the given JSON string against the predefined schema. /// Validates the given JSON string against the schema
/// in `assets/schema.json`.
@visibleForTesting @visibleForTesting
static Future<bool> validateJsonSchema(String jsonString) async { static Future<bool> validateJsonSchema(String jsonString) async {
final String schemaString; final String schemaString;

View File

@@ -588,6 +588,18 @@ void main() {
expect(games[0].ruleset, testGame.ruleset); expect(games[0].ruleset, testGame.ruleset);
}); });
test('parseGamesFromJson() empty list', () {
final jsonMap = {'games': []};
final games = DataTransferService.parseGamesFromJson(jsonMap);
expect(games, isEmpty);
});
test('parseGamesFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final games = DataTransferService.parseGamesFromJson(jsonMap);
expect(games, isEmpty);
});
test('parseGroupsFromJson()', () { test('parseGroupsFromJson()', () {
final playerById = { final playerById = {
testPlayer1.id: testPlayer1, testPlayer1.id: testPlayer1,
@@ -619,6 +631,18 @@ void main() {
expect(groups[0].members[1].id, testPlayer2.id); expect(groups[0].members[1].id, testPlayer2.id);
}); });
test('parseGroupsFromJson() empty list', () {
final jsonMap = {'groups': []};
final groups = DataTransferService.parseGroupsFromJson(jsonMap, {});
expect(groups, isEmpty);
});
test('parseGroupsFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final groups = DataTransferService.parseGroupsFromJson(jsonMap, {});
expect(groups, isEmpty);
});
test('parseGroupsFromJson() ignores invalid player ids', () { test('parseGroupsFromJson() ignores invalid player ids', () {
final playerById = {testPlayer1.id: testPlayer1}; final playerById = {testPlayer1.id: testPlayer1};
@@ -670,6 +694,18 @@ void main() {
expect(teams[0].members[0].id, testPlayer1.id); expect(teams[0].members[0].id, testPlayer1.id);
}); });
test('parseTeamsFromJson() empty list', () {
final jsonMap = {'teams': []};
final teams = DataTransferService.parseTeamsFromJson(jsonMap, {});
expect(teams, isEmpty);
});
test('parseTeamsFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final teams = DataTransferService.parseTeamsFromJson(jsonMap, {});
expect(teams, isEmpty);
});
test('parseMatchesFromJson()', () { test('parseMatchesFromJson()', () {
final playerById = { final playerById = {
testPlayer1.id: testPlayer1, testPlayer1.id: testPlayer1,
@@ -707,6 +743,28 @@ void main() {
expect(matches[0].players.length, 2); expect(matches[0].players.length, 2);
}); });
test('parseMatchesFromJson() empty list', () {
final jsonMap = {'teams': []};
final matches = DataTransferService.parseMatchesFromJson(
jsonMap,
{},
{},
{},
);
expect(matches, isEmpty);
});
test('parseMatchesFromJson() missing key', () {
final jsonMap = <String, dynamic>{};
final matches = DataTransferService.parseMatchesFromJson(
jsonMap,
{},
{},
{},
);
expect(matches, isEmpty);
});
test('parseMatchesFromJson() creates unknown game for missing game', () { test('parseMatchesFromJson() creates unknown game for missing game', () {
final playerById = {testPlayer1.id: testPlayer1}; final playerById = {testPlayer1.id: testPlayer1};
final gameById = <String, Game>{}; final gameById = <String, Game>{};