From e8c50b2f32aba70216942ec56cab68dc2ce8a5bb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 24 Apr 2026 13:10:34 +0200 Subject: [PATCH 01/15] Renamed file --- test/db_tests/values/{score_test.dart => score_entry_test.dart} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/db_tests/values/{score_test.dart => score_entry_test.dart} (100%) diff --git a/test/db_tests/values/score_test.dart b/test/db_tests/values/score_entry_test.dart similarity index 100% rename from test/db_tests/values/score_test.dart rename to test/db_tests/values/score_entry_test.dart -- 2.49.1 From 8df5c24fa8d886567fb97559d187fe3be9e7335a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 24 Apr 2026 13:51:20 +0200 Subject: [PATCH 02/15] Updated group dao + tests --- lib/data/dao/group_dao.dart | 132 ++--- test/db_tests/aggregates/group_test.dart | 617 ++++++++++++----------- 2 files changed, 402 insertions(+), 347 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 0d66ef6..552b138 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -12,43 +12,7 @@ part 'group_dao.g.dart'; class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { GroupDao(super.db); - /// Retrieves all groups from the database. - Future> getAllGroups() async { - final query = select(groupTable); - final result = await query.get(); - return Future.wait( - result.map((groupData) async { - final members = await db.playerGroupDao.getPlayersOfGroup( - groupId: groupData.id, - ); - return Group( - id: groupData.id, - name: groupData.name, - description: groupData.description, - members: members, - createdAt: groupData.createdAt, - ); - }), - ); - } - - /// Retrieves a [Group] by its [groupId], including its members. - Future getGroupById({required String groupId}) async { - final query = select(groupTable)..where((g) => g.id.equals(groupId)); - final result = await query.getSingle(); - - List members = await db.playerGroupDao.getPlayersOfGroup( - groupId: groupId, - ); - - return Group( - id: result.id, - name: result.name, - description: result.description, - members: members, - createdAt: result.createdAt, - ); - } + /* Create */ /// Adds a new group with the given [id] and [name] to the database. /// This method also adds the group's members to the [PlayerGroupTable]. @@ -172,6 +136,65 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { }); } + /* Read */ + + /// Retrieves all groups from the database. + Future> getAllGroups() async { + final query = select(groupTable); + final result = await query.get(); + return Future.wait( + result.map((groupData) async { + final members = await db.playerGroupDao.getPlayersOfGroup( + groupId: groupData.id, + ); + return Group( + id: groupData.id, + name: groupData.name, + description: groupData.description, + members: members, + createdAt: groupData.createdAt, + ); + }), + ); + } + + /// Retrieves a [Group] by its [groupId], including its members. + Future getGroupById({required String groupId}) async { + final query = select(groupTable)..where((g) => g.id.equals(groupId)); + final result = await query.getSingle(); + + List members = await db.playerGroupDao.getPlayersOfGroup( + groupId: groupId, + ); + + return Group( + id: result.id, + name: result.name, + description: result.description, + members: members, + createdAt: result.createdAt, + ); + } + + /// Retrieves the number of groups in the database. + Future getGroupCount() async { + final count = + await (selectOnly(groupTable)..addColumns([groupTable.id.count()])) + .map((row) => row.read(groupTable.id.count())) + .getSingle(); + return count ?? 0; + } + + /// Checks if a group with the given [groupId] exists in the database. + /// Returns `true` if the group exists, `false` otherwise. + Future groupExists({required String groupId}) async { + final query = select(groupTable)..where((g) => g.id.equals(groupId)); + final result = await query.getSingleOrNull(); + return result != null; + } + + /* Delete */ + /// Deletes the group with the given [id] from the database. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future deleteGroup({required String groupId}) async { @@ -180,6 +203,16 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { return rowsAffected > 0; } + /// Deletes all groups from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllGroups() async { + final query = delete(groupTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /* Update */ + /// Updates the name of the group with the given [id] to [newName]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updateGroupName({ @@ -206,31 +239,6 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { return rowsAffected > 0; } - /// Retrieves the number of groups in the database. - Future getGroupCount() async { - final count = - await (selectOnly(groupTable)..addColumns([groupTable.id.count()])) - .map((row) => row.read(groupTable.id.count())) - .getSingle(); - return count ?? 0; - } - - /// Checks if a group with the given [groupId] exists in the database. - /// Returns `true` if the group exists, `false` otherwise. - Future groupExists({required String groupId}) async { - final query = select(groupTable)..where((g) => g.id.equals(groupId)); - final result = await query.getSingleOrNull(); - return result != null; - } - - /// Deletes all groups from the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteAllGroups() async { - final query = delete(groupTable); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - /// Replaces all players in a group with the provided list of players. /// Removes all existing players from the group and adds the new players. /// Also adds any new players to the player table if they don't exist. diff --git a/test/db_tests/aggregates/group_test.dart b/test/db_tests/aggregates/group_test.dart index 3d51a06..9040daf 100644 --- a/test/db_tests/aggregates/group_test.dart +++ b/test/db_tests/aggregates/group_test.dart @@ -35,25 +35,23 @@ void main() { testPlayer4 = Player(name: 'Diana'); testGroup1 = Group( name: 'Test Group', - description: '', members: [testPlayer1, testPlayer2, testPlayer3], + description: 'description of the test group 1', ); testGroup2 = Group( id: 'gr2', name: 'Second Group', - description: '', members: [testPlayer2, testPlayer3, testPlayer4], + description: 'description of the test group 2', ); testGroup3 = Group( id: 'gr2', name: 'Second Group', - description: '', members: [testPlayer2, testPlayer4], ); testGroup4 = Group( id: 'gr2', name: 'Second Group', - description: '', members: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); }); @@ -62,312 +60,361 @@ 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); + group('CREATE', () { + test('Adding and fetching a single group works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); - final fetchedGroup = await database.groupDao.getGroupById( - groupId: testGroup1.id, - ); - - expect(fetchedGroup.id, testGroup1.id); - expect(fetchedGroup.name, testGroup1.name); - expect(fetchedGroup.createdAt, testGroup1.createdAt); - - expect(fetchedGroup.members.length, testGroup1.members.length); - for (int i = 0; i < testGroup1.members.length; i++) { - expect(fetchedGroup.members[i].id, testGroup1.members[i].id); - expect(fetchedGroup.members[i].name, testGroup1.members[i].name); - expect( - fetchedGroup.members[i].createdAt, - testGroup1.members[i].createdAt, + final fetchedGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, ); - } - }); - // Verifies that multiple groups can be added and retrieved with correct members. - test('Adding and fetching multiple groups works correctly', () async { - await database.groupDao.addGroupsAsList( - groups: [testGroup1, testGroup2, testGroup3, testGroup4], - ); + expect(fetchedGroup.id, testGroup1.id); + expect(fetchedGroup.name, testGroup1.name); + expect(fetchedGroup.createdAt, testGroup1.createdAt); - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 2); - - final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2}; - - for (final group in allGroups) { - final testGroup = testGroups[group.id]!; - - expect(group.id, testGroup.id); - expect(group.name, testGroup.name); - expect(group.createdAt, testGroup.createdAt); - - expect(group.members.length, testGroup.members.length); - for (int i = 0; i < testGroup.members.length; i++) { - expect(group.members[i].id, testGroup.members[i].id); - expect(group.members[i].name, testGroup.members[i].name); - expect(group.members[i].createdAt, testGroup.members[i].createdAt); + expect(fetchedGroup.members.length, testGroup1.members.length); + for (int i = 0; i < testGroup1.members.length; i++) { + expect(fetchedGroup.members[i].id, testGroup1.members[i].id); + expect(fetchedGroup.members[i].name, testGroup1.members[i].name); + expect( + fetchedGroup.members[i].createdAt, + testGroup1.members[i].createdAt, + ); } - } + }); + + test('Adding the same group twice does not create duplicates', () async { + await database.groupDao.addGroup(group: testGroup1); + await database.groupDao.addGroup(group: testGroup1); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 1); + + final fetchedGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(fetchedGroup.id, testGroup1.id); + expect(fetchedGroup.members.length, testGroup1.members.length); + }); + + test('addGroup() returns false when group already exists', () async { + final firstAdd = await database.groupDao.addGroup(group: testGroup1); + expect(firstAdd, true); + + final secondAdd = await database.groupDao.addGroup(group: testGroup1); + expect(secondAdd, false); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 1); + }); + + test('Adding and fetching multiple groups works correctly', () async { + await database.groupDao.addGroupsAsList( + groups: [testGroup1, testGroup2, testGroup3, testGroup4], + ); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 2); + + final testGroups = { + testGroup1.id: testGroup1, + testGroup2.id: testGroup2, + }; + + for (final group in allGroups) { + final testGroup = testGroups[group.id]!; + + expect(group.id, testGroup.id); + expect(group.name, testGroup.name); + expect(group.createdAt, testGroup.createdAt); + + expect(group.members.length, testGroup.members.length); + for (int i = 0; i < testGroup.members.length; i++) { + expect(group.members[i].id, testGroup.members[i].id); + expect(group.members[i].name, testGroup.members[i].name); + expect(group.members[i].createdAt, testGroup.members[i].createdAt); + } + } + }); + + test('addGroupsAsList() handles empty list correctly', () async { + await database.groupDao.addGroupsAsList(groups: []); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 0); + }); }); - // Verifies that adding the same group twice does not create duplicates. - test('Adding the same group twice does not create duplicates', () async { - await database.groupDao.addGroup(group: testGroup1); - await database.groupDao.addGroup(group: testGroup1); + group('READ', () { + test('groupExists() works correctly', () async { + var groupExists = await database.groupDao.groupExists( + groupId: testGroup1.id, + ); + expect(groupExists, false); - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 1); + await database.groupDao.addGroup(group: testGroup1); + + groupExists = await database.groupDao.groupExists( + groupId: testGroup1.id, + ); + expect(groupExists, true); + }); + + test('getGroupCount() works correctly', () async { + var count = await database.groupDao.getGroupCount(); + expect(count, 0); + + var added = await database.groupDao.addGroup(group: testGroup1); + expect(added, true); + count = await database.groupDao.getGroupCount(); + expect(count, 1); + + added = await database.groupDao.addGroup(group: testGroup2); + expect(added, true); + count = await database.groupDao.getGroupCount(); + expect(count, 2); + + final removed = await database.groupDao.deleteGroup( + groupId: testGroup1.id, + ); + expect(removed, true); + count = await database.groupDao.getGroupCount(); + expect(count, 1); + }); + + test('getGroupById() throws exception for non-existent group', () async { + expect( + () => database.groupDao.getGroupById(groupId: 'non-existent-id'), + throwsA(isA()), + ); + }); + + test('getAllGroups() returns empty list when no groups exist', () async { + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups, isEmpty); + }); + + test('addGroupsAsList() with duplicate groups only adds once', () async { + await database.groupDao.addGroupsAsList( + groups: [testGroup1, testGroup1, testGroup1], + ); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 1); + }); }); - // Verifies that groupExists returns correct boolean based on group presence. - test('Group existence check works correctly', () async { - var groupExists = await database.groupDao.groupExists( - groupId: testGroup1.id, - ); - expect(groupExists, false); + group('UPDATE', () { + test('updateGroupName() works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); - await database.groupDao.addGroup(group: testGroup1); + const newName = 'New name'; + await database.groupDao.updateGroupName( + groupId: testGroup1.id, + newName: newName, + ); - groupExists = await database.groupDao.groupExists(groupId: testGroup1.id); - expect(groupExists, true); - }); + final result = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(result.name, newName); + }); - // Verifies that deleteGroup removes the group and returns true. - test('Deleting a group works correctly', () async { - await database.groupDao.addGroup(group: testGroup1); - - final groupDeleted = await database.groupDao.deleteGroup( - groupId: testGroup1.id, - ); - expect(groupDeleted, true); - - final groupExists = await database.groupDao.groupExists( - groupId: testGroup1.id, - ); - expect(groupExists, false); - }); - - // Verifies that updateGroupName correctly updates only the name field. - test('Updating a group name works correctly', () async { - await database.groupDao.addGroup(group: testGroup1); - - const newGroupName = 'new group name'; - - await database.groupDao.updateGroupName( - groupId: testGroup1.id, - newName: newGroupName, - ); - - final result = await database.groupDao.getGroupById( - groupId: testGroup1.id, - ); - expect(result.name, newGroupName); - }); - - // Verifies that getGroupCount returns correct count through add/delete operations. - test('Getting the group count works correctly', () async { - final initialCount = await database.groupDao.getGroupCount(); - expect(initialCount, 0); - - await database.groupDao.addGroup(group: testGroup1); - - final groupAdded = await database.groupDao.getGroupCount(); - expect(groupAdded, 1); - - final groupRemoved = await database.groupDao.deleteGroup( - groupId: testGroup1.id, - ); - expect(groupRemoved, true); - - final finalCount = await database.groupDao.getGroupCount(); - expect(finalCount, 0); - }); - - // Verifies that getAllGroups returns an empty list when no groups exist. - test('getAllGroups returns empty list when no groups exist', () async { - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups, isEmpty); - }); - - // Verifies that getGroupById throws StateError for non-existent group ID. - test('getGroupById throws exception for non-existent group', () async { - expect( - () => database.groupDao.getGroupById(groupId: 'non-existent-id'), - throwsA(isA()), - ); - }); - - // Verifies that addGroup returns false when trying to add a duplicate group. - test('addGroup returns false when group already exists', () async { - final firstAdd = await database.groupDao.addGroup(group: testGroup1); - expect(firstAdd, true); - - final secondAdd = await database.groupDao.addGroup(group: testGroup1); - expect(secondAdd, false); - - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 1); - }); - - // Verifies that addGroupsAsList handles an empty list without errors. - test('addGroupsAsList handles empty list correctly', () async { - await database.groupDao.addGroupsAsList(groups: []); - - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 0); - }); - - // Verifies that deleteGroup returns false for a non-existent group ID. - test('deleteGroup returns false for non-existent group', () async { - final deleted = await database.groupDao.deleteGroup( - groupId: 'non-existent-id', - ); - expect(deleted, false); - }); - - // Verifies that updateGroupName returns false for a non-existent group ID. - test('updateGroupName returns false for non-existent group', () async { - final updated = await database.groupDao.updateGroupName( - groupId: 'non-existent-id', - newName: 'New Name', - ); - expect(updated, false); - }); - - // Verifies that updateGroupDescription correctly updates the description field. - test('Updating a group description works correctly', () async { - await database.groupDao.addGroup(group: testGroup1); - - const newDescription = 'This is a new description'; - - final updated = await database.groupDao.updateGroupDescription( - groupId: testGroup1.id, - newDescription: newDescription, - ); - expect(updated, true); - - final result = await database.groupDao.getGroupById( - groupId: testGroup1.id, - ); - expect(result.description, newDescription); - }); - - // Verifies that updateGroupDescription can set the description to null. - test('updateGroupDescription can set description to null', () async { - final groupWithDescription = Group( - name: 'Group with description', - description: 'Initial description', - members: [testPlayer1], - ); - await database.groupDao.addGroup(group: groupWithDescription); - - final updated = await database.groupDao.updateGroupDescription( - groupId: groupWithDescription.id, - newDescription: 'Updated description', - ); - expect(updated, true); - - final result = await database.groupDao.getGroupById( - groupId: groupWithDescription.id, - ); - expect(result.description, 'Updated description'); - }); - - // 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( + test('updateGroupName() returns false for non-existent group', () async { + final updated = await database.groupDao.updateGroupName( groupId: 'non-existent-id', - newDescription: 'New Description', + newName: 'New name', ); 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]); + test('updateGroupDescription() works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); - final countBefore = await database.groupDao.getGroupCount(); - expect(countBefore, 2); + const newDescription = 'New description'; + final updated = await database.groupDao.updateGroupDescription( + groupId: testGroup1.id, + newDescription: newDescription, + ); + expect(updated, true); - final deleted = await database.groupDao.deleteAllGroups(); - expect(deleted, true); + final group = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(group.description, newDescription); + }); - final countAfter = await database.groupDao.getGroupCount(); - expect(countAfter, 0); + 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('Multiple updates to the same group work correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + const newName = 'New name'; + const newDescription = 'New description'; + + await database.groupDao.updateGroupName( + groupId: testGroup1.id, + newName: newName, + ); + await database.groupDao.updateGroupDescription( + groupId: testGroup1.id, + newDescription: newDescription, + ); + + final updatedGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(updatedGroup.name, newName); + expect(updatedGroup.description, newDescription); + }); + + test('replaceGroupPlayers() works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + + final initialGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(initialGroup.members.length, 3); + expect( + initialGroup.members + .map((p) => p.id) + .toList() + .contains(testPlayer1.id), + true, + ); + expect( + initialGroup.members + .map((p) => p.id) + .toList() + .contains(testPlayer2.id), + true, + ); + expect( + initialGroup.members + .map((p) => p.id) + .toList() + .contains(testPlayer3.id), + true, + ); + + final newPlayers = [testPlayer2, testPlayer4]; + final replaced = await database.groupDao.replaceGroupPlayers( + groupId: testGroup1.id, + newPlayers: newPlayers, + ); + expect(replaced, true); + + final updatedGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(updatedGroup.members.length, 2); + + final memberIds = updatedGroup.members.map((p) => p.id).toList(); + expect(memberIds.contains(testPlayer2.id), true); + expect(memberIds.contains(testPlayer4.id), true); + expect(memberIds.contains(testPlayer1.id), false); + expect(memberIds.contains(testPlayer3.id), false); + }); + + test('replaceGroupPlayers() with empty list works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + + final initialGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(initialGroup.members.length, 3); + + final replaced = await database.groupDao.replaceGroupPlayers( + groupId: testGroup1.id, + newPlayers: [], + ); + expect(replaced, true); + + final updatedGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(updatedGroup.members.length, 0); + expect(updatedGroup.members, isEmpty); + }); + + test( + 'replaceGroupPlayers() returns false for non-existent group', + () async { + final replaced = await database.groupDao.replaceGroupPlayers( + groupId: 'non-existent-id', + newPlayers: [testPlayer1], + ); + expect(replaced, false); + }, + ); }); - // Verifies that deleteAllGroups returns false when no groups exist. - test('deleteAllGroups returns false when no groups exist', () async { - final deleted = await database.groupDao.deleteAllGroups(); - expect(deleted, false); + group('DELETE', () { + test('deleteGroup() works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + + final groupDeleted = await database.groupDao.deleteGroup( + groupId: testGroup1.id, + ); + expect(groupDeleted, true); + + final groupExists = await database.groupDao.groupExists( + groupId: testGroup1.id, + ); + expect(groupExists, false); + }); + + test('deleteGroup() returns false for non-existent group', () async { + final deleted = await database.groupDao.deleteGroup( + groupId: 'non-existent-id', + ); + expect(deleted, false); + }); + + test('deleteAllGroups() works correctly', () async { + await database.groupDao.addGroupsAsList( + groups: [testGroup1, testGroup2], + ); + + var count = await database.groupDao.getGroupCount(); + expect(count, 2); + + final deleted = await database.groupDao.deleteAllGroups(); + expect(deleted, true); + + count = await database.groupDao.getGroupCount(); + expect(count, 0); + }); + + test('deleteAllGroups() returns false when no groups exist', () async { + final deleted = await database.groupDao.deleteAllGroups(); + expect(deleted, false); + }); }); - // Verifies that groups with special characters (quotes, emojis) are stored correctly. - test('Group with special characters in name is stored correctly', () async { - final specialGroup = Group( - name: 'Group\'s & "Special" ', - description: 'Description with émojis 🎮🎲', - members: [testPlayer1], - ); - await database.groupDao.addGroup(group: specialGroup); + group('Edge Cases', () { + test('Group with special characters is stored correctly', () async { + final specialGroup = Group( + name: 'Group\'s & "Special" ', + description: 'Description with émojis 🎮🎲', + members: [testPlayer1], + ); + await database.groupDao.addGroup(group: specialGroup); - final fetchedGroup = await database.groupDao.getGroupById( - groupId: specialGroup.id, - ); - expect(fetchedGroup.name, 'Group\'s & "Special" '); - expect(fetchedGroup.description, 'Description with émojis 🎮🎲'); - }); - - // Verifies that a group with an empty members list can be stored and retrieved. - test('Group with empty members list is stored correctly', () async { - final emptyGroup = Group( - name: 'Empty Group', - description: '', - members: [], - ); - await database.groupDao.addGroup(group: emptyGroup); - - final fetchedGroup = await database.groupDao.getGroupById( - groupId: emptyGroup.id, - ); - expect(fetchedGroup.name, 'Empty Group'); - expect(fetchedGroup.members, isEmpty); - }); - - // Verifies that multiple sequential updates to the same group work correctly. - test('Multiple updates to the same group work correctly', () async { - await database.groupDao.addGroup(group: testGroup1); - - await database.groupDao.updateGroupName( - groupId: testGroup1.id, - newName: 'Updated Name', - ); - await database.groupDao.updateGroupDescription( - groupId: testGroup1.id, - newDescription: 'Updated Description', - ); - - final updatedGroup = await database.groupDao.getGroupById( - groupId: testGroup1.id, - ); - expect(updatedGroup.name, 'Updated Name'); - expect(updatedGroup.description, 'Updated Description'); - expect(updatedGroup.members.length, testGroup1.members.length); - }); - - // Verifies that addGroupsAsList with duplicate groups only adds unique ones. - test('addGroupsAsList with duplicate groups only adds once', () async { - await database.groupDao.addGroupsAsList( - groups: [testGroup1, testGroup1, testGroup1], - ); - - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 1); + final fetchedGroup = await database.groupDao.getGroupById( + groupId: specialGroup.id, + ); + expect(fetchedGroup.name, 'Group\'s & "Special" '); + expect(fetchedGroup.description, 'Description with émojis 🎮🎲'); + }); }); }); } -- 2.49.1 From 95cad71ea0550a1571affb71f4c032b064fe627f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 24 Apr 2026 14:29:01 +0200 Subject: [PATCH 03/15] Updated player dao + tests --- lib/data/dao/player_dao.dart | 118 +++-- test/db_tests/entities/player_test.dart | 611 +++++++++++------------- 2 files changed, 357 insertions(+), 372 deletions(-) diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 5d46343..dd46e17 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -10,35 +10,7 @@ part 'player_dao.g.dart'; class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { PlayerDao(super.db); - /// Retrieves all players from the database. - Future> getAllPlayers() async { - final query = select(playerTable); - final result = await query.get(); - return result - .map( - (row) => Player( - id: row.id, - name: row.name, - description: row.description, - createdAt: row.createdAt, - nameCount: row.nameCount, - ), - ) - .toList(); - } - - /// Retrieves a [Player] by their [id]. - Future getPlayerById({required String playerId}) async { - final query = select(playerTable)..where((p) => p.id.equals(playerId)); - final result = await query.getSingle(); - return Player( - id: result.id, - name: result.name, - description: result.description, - createdAt: result.createdAt, - nameCount: result.nameCount, - ); - } + /* Create */ /// Adds a new [player] to the database. /// If a player with the same ID already exists, updates their name to @@ -135,12 +107,15 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { return true; } - /// Deletes the player with the given [id] from the database. - /// Returns `true` if the player was deleted, `false` if the player did not exist. - Future deletePlayer({required String playerId}) async { - final query = delete(playerTable)..where((p) => p.id.equals(playerId)); - final rowsAffected = await query.go(); - return rowsAffected > 0; + /* Read */ + + /// Retrieves the total count of players in the database. + Future getPlayerCount() async { + final count = + await (selectOnly(playerTable)..addColumns([playerTable.id.count()])) + .map((row) => row.read(playerTable.id.count())) + .getSingle(); + return count ?? 0; } /// Checks if a player with the given [playerId] exists in the database. @@ -151,8 +126,40 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { return result != null; } + /// Retrieves all players from the database. + Future> getAllPlayers() async { + final query = select(playerTable); + final result = await query.get(); + return result + .map( + (row) => Player( + id: row.id, + name: row.name, + description: row.description, + createdAt: row.createdAt, + nameCount: row.nameCount, + ), + ) + .toList(); + } + + /// Retrieves a [Player] by their [id]. + Future getPlayerById({required String playerId}) async { + final query = select(playerTable)..where((p) => p.id.equals(playerId)); + final result = await query.getSingle(); + return Player( + id: result.id, + name: result.name, + description: result.description, + createdAt: result.createdAt, + nameCount: result.nameCount, + ); + } + + /* Update */ + /// Updates the name of the player with the given [playerId] to [newName]. - Future updatePlayerName({ + Future updatePlayerName({ required String playerId, required String newName, }) async { @@ -164,9 +171,10 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { ''; final previousNameCount = await getNameCount(name: previousPlayerName); - await (update(playerTable)..where((p) => p.id.equals(playerId))).write( - PlayerTableCompanion(name: Value(newName)), - ); + final rowsAffected = + await (update(playerTable)..where((p) => p.id.equals(playerId))).write( + PlayerTableCompanion(name: Value(newName)), + ); // Update name count for the new name final count = await calculateNameCount(name: newName); @@ -188,17 +196,35 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { ); } } + return rowsAffected > 0; } - /// Retrieves the total count of players in the database. - Future getPlayerCount() async { - final count = - await (selectOnly(playerTable)..addColumns([playerTable.id.count()])) - .map((row) => row.read(playerTable.id.count())) - .getSingle(); - return count ?? 0; + /// Updates the description of the player with the given [playerId] to + /// [newDescription]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updatePlayerDescription({ + required String playerId, + required String newDescription, + }) async { + final rowsAffected = + await (update(playerTable)..where((g) => g.id.equals(playerId))).write( + PlayerTableCompanion(description: Value(newDescription)), + ); + return rowsAffected > 0; } + /* Delete */ + + /// Deletes the player with the given [id] from the database. + /// Returns `true` if the player was deleted, `false` if the player did not exist. + Future deletePlayer({required String playerId}) async { + final query = delete(playerTable)..where((p) => p.id.equals(playerId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /* Name count management */ + /// Retrieves the count of players with the given [name]. Future getNameCount({required String name}) async { final query = select(playerTable)..where((p) => p.name.equals(name)); diff --git a/test/db_tests/entities/player_test.dart b/test/db_tests/entities/player_test.dart index 1aab348..963d10e 100644 --- a/test/db_tests/entities/player_test.dart +++ b/test/db_tests/entities/player_test.dart @@ -24,8 +24,8 @@ void main() { ); withClock(fakeClock, () { - testPlayer1 = Player(name: 'Test Player'); - testPlayer2 = Player(name: 'Second Player'); + testPlayer1 = Player(name: 'Anna', description: 'First test player'); + testPlayer2 = Player(name: 'Bob', description: 'Second test player'); testPlayer3 = Player(name: 'Charlie'); testPlayer4 = Player(name: 'Diana'); }); @@ -35,355 +35,314 @@ 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); - await database.playerDao.addPlayer(player: testPlayer2); + group('CREATE', () { + test('Adding and fetching single player works correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer2); - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 2); + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); - final fetchedPlayer1 = allPlayers.firstWhere( - (g) => g.id == testPlayer1.id, - ); - expect(fetchedPlayer1.name, testPlayer1.name); - expect(fetchedPlayer1.createdAt, testPlayer1.createdAt); + final fetchedPlayer1 = allPlayers.firstWhere( + (g) => g.id == testPlayer1.id, + ); + expect(fetchedPlayer1.name, testPlayer1.name); + expect(fetchedPlayer1.createdAt, testPlayer1.createdAt); + expect(fetchedPlayer1.description, testPlayer1.description); - final fetchedPlayer2 = allPlayers.firstWhere( - (g) => g.id == testPlayer2.id, - ); - expect(fetchedPlayer2.name, testPlayer2.name); - expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); - }); + final fetchedPlayer2 = allPlayers.firstWhere( + (g) => g.id == testPlayer2.id, + ); + expect(fetchedPlayer2.name, testPlayer2.name); + expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); + expect(fetchedPlayer2.description, testPlayer2.description); + }); - // Verifies that multiple players can be added at once and retrieved correctly. - test('Adding and fetching multiple players works correctly', () async { - await database.playerDao.addPlayersAsList( - players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], - ); - - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 4); - - // Map for connecting fetched players with expected players - final testPlayers = { - testPlayer1.id: testPlayer1, - testPlayer2.id: testPlayer2, - testPlayer3.id: testPlayer3, - testPlayer4.id: testPlayer4, - }; - - for (final player in allPlayers) { - final testPlayer = testPlayers[player.id]!; - - expect(player.id, testPlayer.id); - expect(player.name, testPlayer.name); - expect(player.createdAt, testPlayer.createdAt); - } - }); - - // Verifies that adding the same player twice does not create duplicates. - test('Adding the same player twice does not create duplicates', () async { - await database.playerDao.addPlayer(player: testPlayer1); - await database.playerDao.addPlayer(player: testPlayer1); - - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 1); - }); - - // Verifies that playerExists returns correct boolean based on player presence. - test('Player existence check works correctly', () async { - var playerExists = await database.playerDao.playerExists( - playerId: testPlayer1.id, - ); - expect(playerExists, false); - - await database.playerDao.addPlayer(player: testPlayer1); - - playerExists = await database.playerDao.playerExists( - playerId: testPlayer1.id, - ); - expect(playerExists, true); - }); - - // Verifies that deletePlayer removes the player and returns true. - test('Deleting a player works correctly', () async { - await database.playerDao.addPlayer(player: testPlayer1); - final playerDeleted = await database.playerDao.deletePlayer( - playerId: testPlayer1.id, - ); - expect(playerDeleted, true); - - final playerExists = await database.playerDao.playerExists( - playerId: testPlayer1.id, - ); - expect(playerExists, false); - }); - - // Verifies that updatePlayerName correctly updates only the name field. - test('Updating a player name works correctly', () async { - await database.playerDao.addPlayer(player: testPlayer1); - - const newPlayerName = 'new player name'; - - await database.playerDao.updatePlayerName( - playerId: testPlayer1.id, - newName: newPlayerName, - ); - - final result = await database.playerDao.getPlayerById( - playerId: testPlayer1.id, - ); - expect(result.name, newPlayerName); - }); - - // Verifies that getPlayerCount returns correct count through add/delete operations. - test('Getting the player count works correctly', () async { - var playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 0); - - await database.playerDao.addPlayer(player: testPlayer1); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 1); - - await database.playerDao.addPlayer(player: testPlayer2); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 2); - - await database.playerDao.deletePlayer(playerId: testPlayer1.id); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 1); - - await database.playerDao.deletePlayer(playerId: testPlayer2.id); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 0); - }); - - // Verifies that getAllPlayers returns an empty list when no players exist. - test('getAllPlayers returns empty list when no players exist', () async { - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers, isEmpty); - }); - - // Verifies that getPlayerById returns the correct player. - test('getPlayerById returns correct player', () async { - await database.playerDao.addPlayer(player: testPlayer1); - await database.playerDao.addPlayer(player: testPlayer2); - - final fetchedPlayer = await database.playerDao.getPlayerById( - playerId: testPlayer1.id, - ); - - expect(fetchedPlayer.id, testPlayer1.id); - expect(fetchedPlayer.name, testPlayer1.name); - expect(fetchedPlayer.createdAt, testPlayer1.createdAt); - expect(fetchedPlayer.description, testPlayer1.description); - }); - - // Verifies that getPlayerById throws StateError for non-existent player ID. - test('getPlayerById throws exception for non-existent player', () async { - expect( - () => database.playerDao.getPlayerById(playerId: 'non-existent-id'), - throwsA(isA()), - ); - }); - - // Verifies that addPlayer returns false when trying to add a duplicate player. - test('addPlayer returns false when player already exists', () async { - final firstAdd = await database.playerDao.addPlayer(player: testPlayer1); - expect(firstAdd, true); - - final secondAdd = await database.playerDao.addPlayer(player: testPlayer1); - expect(secondAdd, false); - }); - - // Verifies that addPlayersAsList handles empty list correctly. - test('addPlayersAsList handles empty list correctly', () async { - final result = await database.playerDao.addPlayersAsList(players: []); - expect(result, false); - - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers, isEmpty); - }); - - // Verifies that addPlayersAsList ignores duplicate player IDs. - test('addPlayersAsList with duplicate IDs ignores duplicates', () async { - await database.playerDao.addPlayersAsList( - players: [testPlayer1, testPlayer1, testPlayer2], - ); - - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 2); - }); - - // Verifies that deletePlayer returns false for non-existent player. - test('deletePlayer returns false for non-existent player', () async { - final result = await database.playerDao.deletePlayer( - playerId: 'non-existent-id', - ); - expect(result, false); - }); - - // Verifies that updatePlayerName does nothing for non-existent player (no exception). - test('updatePlayerName does nothing for non-existent player', () async { - // Should not throw, just do nothing - await database.playerDao.updatePlayerName( - playerId: 'non-existent-id', - newName: 'New Name', - ); - - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers, isEmpty); - }); - - // Verifies that deleteAllPlayers removes all players. - test('deleteAllPlayers removes all players', () async { - await database.playerDao.addPlayersAsList( - players: [testPlayer1, testPlayer2, testPlayer3], - ); - - var playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 3); - - final result = await database.playerDao.deleteAllPlayers(); - expect(result, true); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 0); - }); - - // Verifies that deleteAllPlayers returns false when no players exist. - test('deleteAllPlayers returns false when no players exist', () async { - final result = await database.playerDao.deleteAllPlayers(); - expect(result, false); - }); - - // 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('Adding and fetching multiple players works correctly', () async { + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); - await database.playerDao.addPlayer(player: specialPlayer); + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 4); + + // Map for connecting fetched players with expected players + final testPlayers = { + testPlayer1.id: testPlayer1, + testPlayer2.id: testPlayer2, + testPlayer3.id: testPlayer3, + testPlayer4.id: testPlayer4, + }; + + for (final player in allPlayers) { + final testPlayer = testPlayers[player.id]!; + + expect(player.id, testPlayer.id); + expect(player.name, testPlayer.name); + expect(player.createdAt, testPlayer.createdAt); + expect(player.description, testPlayer.description); + } + }); + + test('Adding the same player twice does not create duplicates', () async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer1); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 1); + }); + + test('addPlayer() returns false when player already exists', () async { + var added = await database.playerDao.addPlayer(player: testPlayer1); + expect(added, true); + + added = await database.playerDao.addPlayer(player: testPlayer1); + expect(added, false); + }); + + test('addPlayersAsList() handles empty list correctly', () async { + final added = await database.playerDao.addPlayersAsList(players: []); + expect(added, false); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers, isEmpty); + }); + + test( + 'addPlayersAsList() with duplicate IDs ignores duplicates', + () async { + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer1, testPlayer2], + ); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); + }, + ); + + test( + 'Player with special characters in name is stored correctly', + () async { + final specialPlayer = Player( + name: 'Test!@#\$%^&*()_+-=[]{}|;\':"😎,.<>?/`~', + ); + + await database.playerDao.addPlayer(player: specialPlayer); + + final fetchedPlayer = await database.playerDao.getPlayerById( + playerId: specialPlayer.id, + ); + expect(fetchedPlayer.name, specialPlayer.name); + }, + ); + }); + + group('READ', () { + test('getPlayerCount() works correctly', () async { + var playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); + + await database.playerDao.addPlayer(player: testPlayer1); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 1); + + await database.playerDao.addPlayer(player: testPlayer2); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 2); + + await database.playerDao.deletePlayer(playerId: testPlayer1.id); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 1); + + await database.playerDao.deletePlayer(playerId: testPlayer2.id); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); + }); + + test('playerExists() works correctly', () async { + var playerExists = await database.playerDao.playerExists( + playerId: testPlayer1.id, + ); + expect(playerExists, false); + + await database.playerDao.addPlayer(player: testPlayer1); + playerExists = await database.playerDao.playerExists( + playerId: testPlayer1.id, + ); + expect(playerExists, true); + }); + + test( + 'getAllPlayers() returns empty list when no players exist', + () async { + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers, isEmpty); + }, + ); + + test('getPlayerById() returns correct player', () async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer2); final fetchedPlayer = await database.playerDao.getPlayerById( - playerId: specialPlayer.id, + playerId: testPlayer1.id, ); - expect(fetchedPlayer.name, specialPlayer.name); - }, - ); - // Verifies that a player with description is stored correctly. - test('Player with description is stored correctly', () async { - final playerWithDescription = Player( - name: 'Described Player', - description: 'This is a test description', + expect(fetchedPlayer.id, testPlayer1.id); + expect(fetchedPlayer.name, testPlayer1.name); + expect(fetchedPlayer.createdAt, testPlayer1.createdAt); + expect(fetchedPlayer.description, testPlayer1.description); + }); + + test( + 'getPlayerById() throws exception for non-existent player', + () async { + expect( + () => database.playerDao.getPlayerById(playerId: 'non-existent-id'), + throwsA(isA()), + ); + }, ); - - await database.playerDao.addPlayer(player: playerWithDescription); - - final fetchedPlayer = await database.playerDao.getPlayerById( - playerId: playerWithDescription.id, - ); - expect(fetchedPlayer.name, playerWithDescription.name); - expect(fetchedPlayer.description, playerWithDescription.description); }); - // 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: '', + group('UPDATE', () { + test('updatePlayerName() works correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); + + const newName = 'New name'; + + await database.playerDao.updatePlayerName( + playerId: testPlayer1.id, + newName: newName, + ); + + final player = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(player.name, newName); + }); + + test('updatePlayerName() does nothing for non-existent player', () async { + final updated = await database.playerDao.updatePlayerName( + playerId: 'non-existent-id', + newName: 'New name', + ); + expect(updated, false); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers, isEmpty); + }); + + test('updatePlayerDescription() works correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); + + const newDescription = 'New description'; + + final updated = await database.playerDao.updatePlayerDescription( + playerId: testPlayer1.id, + newDescription: newDescription, + ); + expect(updated, true); + + final player = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(player.description, newDescription); + }); + + test( + 'updatePlayerDescription() does nothing for non-existent player', + () async { + final updated = await database.playerDao.updatePlayerDescription( + playerId: 'non-existent-id', + newDescription: 'New description', + ); + expect(updated, false); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers, isEmpty); + }, ); - await database.playerDao.addPlayer(player: playerWithoutDescription); + test('Multiple updates to the same player work correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); - final fetchedPlayer = await database.playerDao.getPlayerById( - playerId: playerWithoutDescription.id, - ); - expect(fetchedPlayer.description, ''); + await database.playerDao.updatePlayerName( + playerId: testPlayer1.id, + newName: 'First Update', + ); + + var fetchedPlayer = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(fetchedPlayer.name, 'First Update'); + + await database.playerDao.updatePlayerName( + playerId: testPlayer1.id, + newName: 'Second Update', + ); + + fetchedPlayer = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(fetchedPlayer.name, 'Second Update'); + + await database.playerDao.updatePlayerDescription( + playerId: testPlayer1.id, + newDescription: 'Third Update', + ); + + fetchedPlayer = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(fetchedPlayer.description, 'Third Update'); + }); }); - // Verifies that multiple updates to the same player work correctly. - test('Multiple updates to the same player work correctly', () async { - await database.playerDao.addPlayer(player: testPlayer1); + group('DELETE', () { + test('deletePlayer() works correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); + final playerDeleted = await database.playerDao.deletePlayer( + playerId: testPlayer1.id, + ); + expect(playerDeleted, true); - await database.playerDao.updatePlayerName( - playerId: testPlayer1.id, - newName: 'First Update', - ); + final playerExists = await database.playerDao.playerExists( + playerId: testPlayer1.id, + ); + expect(playerExists, false); + }); - var fetchedPlayer = await database.playerDao.getPlayerById( - playerId: testPlayer1.id, - ); - expect(fetchedPlayer.name, 'First Update'); + test('deletePlayer() returns false for non-existent player', () async { + final deleted = await database.playerDao.deletePlayer( + playerId: 'non-existent-id', + ); + expect(deleted, false); + }); - await database.playerDao.updatePlayerName( - playerId: testPlayer1.id, - newName: 'Second Update', - ); + test('deleteAllPlayers() removes all players', () async { + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2, testPlayer3], + ); - fetchedPlayer = await database.playerDao.getPlayerById( - playerId: testPlayer1.id, - ); - expect(fetchedPlayer.name, 'Second Update'); + var playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 3); - await database.playerDao.updatePlayerName( - playerId: testPlayer1.id, - newName: 'Third Update', - ); + final deleted = await database.playerDao.deleteAllPlayers(); + expect(deleted, true); - fetchedPlayer = await database.playerDao.getPlayerById( - playerId: testPlayer1.id, - ); - expect(fetchedPlayer.name, 'Third Update'); + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); + }); + + test('deleteAllPlayers() returns false when no players exist', () async { + final deleted = await database.playerDao.deleteAllPlayers(); + expect(deleted, false); + }); }); - // Verifies that a player with empty string name is stored correctly. - test('Player with empty string name is stored correctly', () async { - final emptyNamePlayer = Player(name: ''); - - await database.playerDao.addPlayer(player: emptyNamePlayer); - - final fetchedPlayer = await database.playerDao.getPlayerById( - playerId: emptyNamePlayer.id, - ); - expect(fetchedPlayer.name, ''); - }); - - // Verifies that a player with very long name is stored correctly. - test('Player with very long name is stored correctly', () async { - final longName = 'A' * 1000; - final longNamePlayer = Player(name: longName); - - await database.playerDao.addPlayer(player: longNamePlayer); - - final fetchedPlayer = await database.playerDao.getPlayerById( - playerId: longNamePlayer.id, - ); - expect(fetchedPlayer.name, longName); - }); - - // Verifies that addPlayer returns true on first add. - test('addPlayer returns true when player is added successfully', () async { - final result = await database.playerDao.addPlayer(player: testPlayer1); - expect(result, true); - - final playerExists = await database.playerDao.playerExists( - playerId: testPlayer1.id, - ); - expect(playerExists, true); - }); - - group('Name Count Tests', () { - test('Single player gets initialized wih name count 0', () async { + group('NAME COUNT', () { + test('Single player gets initialized wih name count 0', () async { await database.playerDao.addPlayer(player: testPlayer1); final player = await database.playerDao.getPlayerById( @@ -392,7 +351,7 @@ void main() { expect(player.nameCount, 0); }); - test('Multiple players get initialized wih name count 0', () async { + test('Multiple players get initialized wih name count 0', () async { await database.playerDao.addPlayersAsList( players: [testPlayer1, testPlayer2], ); -- 2.49.1 From 92577e4ed96dc4a655130ab895cf9de912291295 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 24 Apr 2026 16:01:14 +0200 Subject: [PATCH 04/15] Updated games dao + tests --- lib/data/dao/game_dao.dart | 214 +++---- test/db_tests/entities/game_test.dart | 799 +++++++++++--------------- 2 files changed, 446 insertions(+), 567 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index f07e2c7..099c501 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -10,39 +10,7 @@ part 'game_dao.g.dart'; class GameDao extends DatabaseAccessor with _$GameDaoMixin { GameDao(super.db); - /// Retrieves all games from the database. - Future> getAllGames() async { - final query = select(gameTable); - final result = await query.get(); - return result - .map( - (row) => Game( - id: row.id, - name: row.name, - ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset), - description: row.description, - color: GameColor.values.firstWhere((e) => e.name == row.color), - icon: row.icon, - createdAt: row.createdAt, - ), - ) - .toList(); - } - - /// Retrieves a [Game] by its [gameId]. - Future getGameById({required String gameId}) async { - final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingle(); - return Game( - id: result.id, - name: result.name, - ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset), - description: result.description, - color: GameColor.values.firstWhere((e) => e.name == result.color), - icon: result.icon, - createdAt: result.createdAt, - ); - } + /* Create */ /// Adds a new [game] to the database. /// If a game with the same ID already exists, no action is taken. @@ -94,71 +62,7 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { return true; } - /// Deletes the game with the given [gameId] from the database. - /// Returns `true` if the game was deleted, `false` if the game did not exist. - Future deleteGame({required String gameId}) async { - final query = delete(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - - /// Checks if a game with the given [gameId] exists in the database. - /// Returns `true` if the game exists, `false` otherwise. - Future gameExists({required String gameId}) async { - final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingleOrNull(); - return result != null; - } - - /// 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)), - ); - } - - /// Updates the ruleset of the game with the given [gameId]. - Future updateGameRuleset({ - required String gameId, - required Ruleset newRuleset, - }) async { - await (update(gameTable)..where((g) => g.id.equals(gameId))).write( - GameTableCompanion(ruleset: Value(newRuleset.name)), - ); - } - - /// Updates the description of the game with the given [gameId]. - Future updateGameDescription({ - required String gameId, - required String newDescription, - }) async { - await (update(gameTable)..where((g) => g.id.equals(gameId))).write( - GameTableCompanion(description: Value(newDescription)), - ); - } - - /// 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)), - ); - } - - /// 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)), - ); - } + /* Read */ /// Retrieves the total count of games in the database. Future getGameCount() async { @@ -169,6 +73,120 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { return count ?? 0; } + /// Checks if a game with the given [gameId] exists in the database. + /// Returns `true` if the game exists, `false` otherwise. + Future gameExists({required String gameId}) async { + final query = select(gameTable)..where((g) => g.id.equals(gameId)); + final result = await query.getSingleOrNull(); + return result != null; + } + + /// Retrieves all games from the database. + Future> getAllGames() async { + final query = select(gameTable); + final result = await query.get(); + return result + .map( + (row) => Game( + id: row.id, + name: row.name, + ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset), + description: row.description, + color: GameColor.values.firstWhere((e) => e.name == row.color), + icon: row.icon, + createdAt: row.createdAt, + ), + ) + .toList(); + } + + /// Retrieves a [Game] by its [gameId]. + Future getGameById({required String gameId}) async { + final query = select(gameTable)..where((g) => g.id.equals(gameId)); + final result = await query.getSingle(); + return Game( + id: result.id, + name: result.name, + ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset), + description: result.description, + color: GameColor.values.firstWhere((e) => e.name == result.color), + icon: result.icon, + createdAt: result.createdAt, + ); + } + + /* Update */ + + /// Updates the name of the game with the given [gameId] to [newName]. + Future updateGameName({ + required String gameId, + required String newName, + }) async { + final rowsAffected = + await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + GameTableCompanion(name: Value(newName)), + ); + return rowsAffected > 0; + } + + /// Updates the ruleset of the game with the given [gameId]. + Future updateGameRuleset({ + required String gameId, + required Ruleset newRuleset, + }) async { + final rowsAffected = + await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + GameTableCompanion(ruleset: Value(newRuleset.name)), + ); + return rowsAffected > 0; + } + + /// Updates the description of the game with the given [gameId]. + Future updateGameDescription({ + required String gameId, + required String newDescription, + }) async { + final rowsAffected = + await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + GameTableCompanion(description: Value(newDescription)), + ); + return rowsAffected > 0; + } + + /// Updates the color of the game with the given [gameId]. + Future updateGameColor({ + required String gameId, + required GameColor newColor, + }) async { + final rowsAffected = + await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + GameTableCompanion(color: Value(newColor.name)), + ); + return rowsAffected > 0; + } + + /// Updates the icon of the game with the given [gameId]. + Future updateGameIcon({ + required String gameId, + required String newIcon, + }) async { + final rowsAffected = + await (update(gameTable)..where((g) => g.id.equals(gameId))).write( + GameTableCompanion(icon: Value(newIcon)), + ); + return rowsAffected > 0; + } + + /* Delete */ + + /// Deletes the game with the given [gameId] from the database. + /// Returns `true` if the game was deleted, `false` if the game did not exist. + Future deleteGame({required String gameId}) async { + final query = delete(gameTable)..where((g) => g.id.equals(gameId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + /// Deletes all games from the database. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future deleteAllGames() async { diff --git a/test/db_tests/entities/game_test.dart b/test/db_tests/entities/game_test.dart index b00dba1..9f94910 100644 --- a/test/db_tests/entities/game_test.dart +++ b/test/db_tests/entities/game_test.dart @@ -54,492 +54,353 @@ 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(); - expect(allGames, isEmpty); - }); + group('CREATE', () { + test('Adding and fetching a single game works correctly', () async { + final added = await database.gameDao.addGame(game: testGame1); + expect(added, true); - // Verifies that a single game can be added and retrieved with all fields intact. - test('Adding and fetching a single game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + final game = await database.gameDao.getGameById(gameId: testGame1.id); + expect(game.id, testGame1.id); + expect(game.name, testGame1.name); + expect(game.ruleset, testGame1.ruleset); + expect(game.description, testGame1.description); + expect(game.color, testGame1.color); + expect(game.icon, testGame1.icon); + expect(game.createdAt, testGame1.createdAt); + }); - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 1); - expect(allGames.first.id, testGame1.id); - expect(allGames.first.name, testGame1.name); - expect(allGames.first.ruleset, testGame1.ruleset); - expect(allGames.first.description, testGame1.description); - expect(allGames.first.color, testGame1.color); - expect(allGames.first.icon, testGame1.icon); - expect(allGames.first.createdAt, testGame1.createdAt); - }); + test('Adding and fetching multiple games works correctly', () async { + final added = await database.gameDao.addGamesAsList( + games: [testGame1, testGame2, testGame3], + ); + expect(added, true); - // Verifies that multiple games can be added and retrieved correctly. - test('Adding and fetching multiple games works correctly', () async { - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGame2); - await database.gameDao.addGame(game: testGame3); + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 3); - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 3); + // Map for connecting fetched games with expected games + final testGames = { + testGame1.id: testGame1, + testGame2.id: testGame2, + testGame3.id: testGame3, + }; - final names = allGames.map((g) => g.name).toList(); - expect(names, containsAll(['Chess', 'Poker', 'Monopoly'])); - }); + for (final game in allGames) { + final testGame = testGames[game.id]!; - // Verifies that getGameById returns the correct game with all properties. - test('getGameById returns correct game', () async { - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGame2); + expect(game.id, testGame.id); + expect(game.name, testGame.name); + expect(game.createdAt, testGame.createdAt); + expect(game.description, testGame.description); + expect(game.ruleset, testGame.ruleset); + expect(game.color, testGame.color); + expect(game.icon, testGame.icon); + } + }); - final game = await database.gameDao.getGameById(gameId: testGame2.id); - expect(game.id, testGame2.id); - expect(game.name, testGame2.name); - expect(game.ruleset, testGame2.ruleset); - expect(game.description, testGame2.description); - expect(game.color, testGame2.color); - expect(game.icon, testGame2.icon); - }); + test('addGamesAsList() returns false for empty list', () async { + final result = await database.gameDao.addGamesAsList(games: []); + expect(result, false); - // 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'), - throwsA(isA()), + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 0); + }); + + test('addGamesAsList() ignores duplicate games', () async { + final added = await database.gameDao.addGamesAsList( + games: [testGame1, testGame2, testGame1], + ); + expect(added, true); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 2); + }); + + test( + 'Game with special characters in name is stored correctly', + () async { + final specialGame = Game( + name: 'Game\'s & "Special" ', + ruleset: Ruleset.multipleWinners, + description: 'Description with émojis 🎮🎲', + color: GameColor.purple, + icon: '', + ); + await database.gameDao.addGame(game: specialGame); + + final fetchedGame = await database.gameDao.getGameById( + gameId: specialGame.id, + ); + expect(fetchedGame.name, 'Game\'s & "Special" '); + expect(fetchedGame.description, 'Description with émojis 🎮🎲'); + }, ); }); - // Verifies that addGame returns true when a game is successfully added. - test('addGame returns true when game is added successfully', () async { - final result = await database.gameDao.addGame(game: testGame1); - expect(result, true); + group('READ', () { + test('getGameById() works correctly', () async { + await database.gameDao.addGame(game: testGame1); - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 1); + final game = await database.gameDao.getGameById(gameId: testGame1.id); + expect(game.id, testGame2.id); + expect(game.name, testGame2.name); + expect(game.ruleset, testGame2.ruleset); + expect(game.description, testGame2.description); + expect(game.color, testGame2.color); + expect(game.icon, testGame2.icon); + }); + + test('getGameById() throws exception for non-existent game', () async { + expect( + () => database.gameDao.getGameById(gameId: 'non-existent-id'), + throwsA(isA()), + ); + }); + + test('gameExists() works correctly', () async { + var exists = await database.gameDao.gameExists(gameId: testGame1.id); + expect(exists, false); + + await database.gameDao.addGame(game: testGame1); + exists = await database.gameDao.gameExists(gameId: testGame1.id); + expect(exists, true); + }); + + test('getAllGames() returns empty list when no games exist', () async { + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + test('getGameCount() works correctly', () async { + var count = await database.gameDao.getGameCount(); + expect(count, 0); + + await database.gameDao.addGame(game: testGame1); + count = await database.gameDao.getGameCount(); + expect(count, 1); + + await database.gameDao.addGame(game: testGame2); + count = await database.gameDao.getGameCount(); + expect(count, 2); + + await database.gameDao.deleteGame(gameId: testGame1.id); + count = await database.gameDao.getGameCount(); + expect(count, 1); + }); }); - // Verifies that addGame returns false when trying to add a duplicate game. - test('addGame returns false when game already exists', () async { - final firstAdd = await database.gameDao.addGame(game: testGame1); - expect(firstAdd, true); + group('UPDATE', () { + test('updateGameName() updates the name correctly', () async { + await database.gameDao.addGame(game: testGame1); + const newName = 'New name'; - final secondAdd = await database.gameDao.addGame(game: testGame1); - expect(secondAdd, false); + final updated = await database.gameDao.updateGameName( + gameId: testGame1.id, + newName: newName, + ); + expect(updated, true); - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 1); + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.name, newName); + }); + + test('updateGameName() does nothing for non-existent game', () async { + final updated = await database.gameDao.updateGameName( + gameId: 'non-existent-id', + newName: 'New name', + ); + expect(updated, false); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + test('updateGameRuleset() updates the ruleset correctly', () async { + await database.gameDao.addGame(game: testGame1); + const ruleset = Ruleset.highestScore; + + final updated = await database.gameDao.updateGameRuleset( + gameId: testGame1.id, + newRuleset: ruleset, + ); + expect(updated, true); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.ruleset, ruleset); + }); + + test('updateGameRuleset() does nothing for non-existent game', () async { + final updated = await database.gameDao.updateGameRuleset( + gameId: 'non-existent-id', + newRuleset: Ruleset.lowestScore, + ); + expect(updated, false); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + test( + 'updateGameDescription() updates the description correctly', + () async { + await database.gameDao.addGame(game: testGame1); + const newDescription = 'New description'; + + final updated = await database.gameDao.updateGameDescription( + gameId: testGame1.id, + newDescription: newDescription, + ); + expect(updated, true); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.description, newDescription); + }, + ); + + test( + 'updateGameDescription() does nothing for non-existent game', + () async { + final updated = await database.gameDao.updateGameDescription( + gameId: 'non-existent-id', + newDescription: 'New description', + ); + expect(updated, false); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }, + ); + + test('updateGameColor() works correctly', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameColor( + gameId: testGame1.id, + newColor: GameColor.green, + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.color, GameColor.green); + }); + + test('updateGameColor() does nothing for non-existent game', () async { + final updated = await database.gameDao.updateGameColor( + gameId: 'non-existent-id', + newColor: GameColor.green, + ); + expect(updated, false); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + test('updateGameIcon() works correctly', () async { + await database.gameDao.addGame(game: testGame1); + const newIcon = 'new_chess_icon'; + + final updated = await database.gameDao.updateGameIcon( + gameId: testGame1.id, + newIcon: newIcon, + ); + expect(updated, true); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.icon, newIcon); + }); + + test('updateGameIcon() does nothing for non-existent game', () async { + final updated = await database.gameDao.updateGameIcon( + gameId: 'non-existent-id', + newIcon: 'New icon', + ); + expect(updated, false); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + test('Multiple updates to the same game work correctly', () async { + await database.gameDao.addGame(game: testGame1); + + const newName = 'New name'; + await database.gameDao.updateGameName( + gameId: testGame1.id, + newName: newName, + ); + + const newGameColor = GameColor.teal; + await database.gameDao.updateGameColor( + gameId: testGame1.id, + newColor: newGameColor, + ); + + const newDescription = 'New description'; + await database.gameDao.updateGameDescription( + gameId: testGame1.id, + newDescription: newDescription, + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + + // Changed values + expect(updatedGame.name, newName); + expect(updatedGame.color, newGameColor); + expect(updatedGame.description, newDescription); + + // Staying the same + expect(updatedGame.ruleset, testGame1.ruleset); + expect(updatedGame.icon, testGame1.icon); + }); }); - - // 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 result = await database.gameDao.addGame(game: gameWithNulls); - expect(result, true); - - final fetchedGame = await database.gameDao.getGameById( - gameId: gameWithNulls.id, - ); - expect(fetchedGame.name, 'Simple Game'); - expect(fetchedGame.description, 'A simple game'); - expect(fetchedGame.color, GameColor.green); - expect(fetchedGame.icon, ''); - }); - - // Verifies that multiple games can be added at once using addGamesAsList. - test('addGamesAsList adds multiple games correctly', () async { - final result = await database.gameDao.addGamesAsList( - games: [testGame1, testGame2, testGame3], - ); - expect(result, true); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 3); - }); - - // Verifies that addGamesAsList returns false when given an empty list. - test('addGamesAsList returns false for empty list', () async { - final result = await database.gameDao.addGamesAsList(games: []); - expect(result, false); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 0); - }); - - // Verifies that addGamesAsList ignores duplicate games when adding. - test('addGamesAsList ignores duplicate games', () async { - await database.gameDao.addGame(game: testGame1); - - final result = await database.gameDao.addGamesAsList( - games: [testGame1, testGame2], - ); - expect(result, true); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 2); - }); - - // Verifies that deleteGame returns true and removes the game from database. - test('deleteGame returns true when game is deleted', () async { - await database.gameDao.addGame(game: testGame1); - - final result = await database.gameDao.deleteGame(gameId: testGame1.id); - expect(result, true); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames, isEmpty); - }); - - // Verifies that deleteGame returns false for a non-existent game ID. - test('deleteGame returns false for non-existent game', () async { - final result = await database.gameDao.deleteGame( - gameId: 'non-existent-id', - ); - expect(result, false); - }); - - // Verifies that deleteGame only removes the specified game, leaving others intact. - test('deleteGame only deletes the specified game', () async { - await database.gameDao.addGamesAsList( - games: [testGame1, testGame2, testGame3], - ); - - await database.gameDao.deleteGame(gameId: testGame2.id); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 2); - expect(allGames.any((g) => g.id == testGame2.id), false); - expect(allGames.any((g) => g.id == testGame1.id), true); - expect(allGames.any((g) => g.id == testGame3.id), true); - }); - - // Verifies that gameExists returns true when the game exists in database. - test('gameExists returns true for existing game', () async { - await database.gameDao.addGame(game: testGame1); - - final exists = await database.gameDao.gameExists(gameId: testGame1.id); - expect(exists, true); - }); - - // Verifies that gameExists returns false for a non-existent game ID. - test('gameExists returns false for non-existent game', () async { - final exists = await database.gameDao.gameExists( - gameId: 'non-existent-id', - ); - expect(exists, false); - }); - - // Verifies that gameExists returns false after a game has been deleted. - test('gameExists returns false after game is deleted', () async { - await database.gameDao.addGame(game: testGame1); - await database.gameDao.deleteGame(gameId: testGame1.id); - - final exists = await database.gameDao.gameExists(gameId: testGame1.id); - expect(exists, false); - }); - - // Verifies that updateGameName correctly updates only the name field. - test('updateGameName updates the name correctly', () async { - await database.gameDao.addGame(game: testGame1); - - await database.gameDao.updateGameName( - gameId: testGame1.id, - newName: 'Updated Chess', - ); - - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.name, 'Updated Chess'); - expect(updatedGame.ruleset, testGame1.ruleset); - }); - - // Verifies that updateGameName does nothing when game doesn't exist. - test('updateGameName does nothing for non-existent game', () async { - await database.gameDao.updateGameName( - gameId: 'non-existent-id', - newName: 'New Name', - ); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames, isEmpty); - }); - - // Verifies that updateGameRuleset correctly updates only the ruleset field. - test('updateGameRuleset updates the ruleset correctly', () async { - await database.gameDao.addGame(game: testGame1); - - await database.gameDao.updateGameRuleset( - gameId: testGame1.id, - newRuleset: Ruleset.highestScore, - ); - - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.ruleset, Ruleset.highestScore); - expect(updatedGame.name, testGame1.name); - }); - - // Verifies that updateGameRuleset does nothing when game doesn't exist. - test('updateGameRuleset does nothing for non-existent game', () async { - await database.gameDao.updateGameRuleset( - gameId: 'non-existent-id', - newRuleset: Ruleset.lowestScore, - ); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames, isEmpty); - }); - - // Verifies that updateGameDescription correctly updates the description. - test('updateGameDescription updates the description correctly', () async { - await database.gameDao.addGame(game: testGame1); - - await database.gameDao.updateGameDescription( - gameId: testGame1.id, - newDescription: 'An updated description', - ); - - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.description, 'An updated description'); - }); - - // Verifies that updateGameDescription can set the description to an empty string. - test('updateGameDescription can set description to empty string', () async { - await database.gameDao.addGame(game: testGame1); - - await database.gameDao.updateGameDescription( - gameId: testGame1.id, - newDescription: '', - ); - - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.description, ''); - }); - - // Verifies that updateGameDescription does nothing when game doesn't exist. - test('updateGameDescription does nothing for non-existent game', () async { - await database.gameDao.updateGameDescription( - gameId: 'non-existent-id', - newDescription: 'New Description', - ); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames, isEmpty); - }); - - // Verifies that updateGameColor correctly updates the color value. - test('updateGameColor updates the color correctly', () async { - await database.gameDao.addGame(game: testGame1); - - await database.gameDao.updateGameColor( - gameId: testGame1.id, - newColor: GameColor.green, - ); - - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.color, GameColor.green); - }); - - // Verifies that updateGameColor does nothing when game doesn't exist. - test('updateGameColor does nothing for non-existent game', () async { - await database.gameDao.updateGameColor( - gameId: 'non-existent-id', - newColor: GameColor.green, - ); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames, isEmpty); - }); - - // Verifies that updateGameIcon correctly updates the icon value. - test('updateGameIcon updates the icon correctly', () async { - await database.gameDao.addGame(game: testGame1); - - await database.gameDao.updateGameIcon( - gameId: testGame1.id, - newIcon: 'new_chess_icon', - ); - - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.icon, 'new_chess_icon'); - }); - - // Verifies that updateGameIcon can update the icon. - test('updateGameIcon updates icon correctly', () async { - await database.gameDao.addGame(game: testGame1); - - await database.gameDao.updateGameIcon( - gameId: testGame1.id, - newIcon: 'new_icon', - ); - - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.icon, 'new_icon'); - }); - - // Verifies that updateGameIcon does nothing when game doesn't exist. - test('updateGameIcon does nothing for non-existent game', () async { - await database.gameDao.updateGameIcon( - gameId: 'non-existent-id', - newIcon: 'some_icon', - ); - - final allGames = await database.gameDao.getAllGames(); - expect(allGames, isEmpty); - }); - - // Verifies that getGameCount returns 0 when no games exist. - test('getGameCount returns 0 when no games exist', () async { - final count = await database.gameDao.getGameCount(); - expect(count, 0); - }); - - // Verifies that getGameCount returns the correct count after adding games. - test('getGameCount returns correct count after adding games', () async { - await database.gameDao.addGamesAsList( - games: [testGame1, testGame2, testGame3], - ); - - final count = await database.gameDao.getGameCount(); - expect(count, 3); - }); - - // Verifies that getGameCount updates correctly after deleting a game. - test('getGameCount updates correctly after deletion', () async { - await database.gameDao.addGamesAsList(games: [testGame1, testGame2]); - - final countBefore = await database.gameDao.getGameCount(); - expect(countBefore, 2); - - await database.gameDao.deleteGame(gameId: testGame1.id); - - final countAfter = await database.gameDao.getGameCount(); - expect(countAfter, 1); - }); - - // Verifies that deleteAllGames removes all games from the database. - test('deleteAllGames removes all games', () async { - await database.gameDao.addGamesAsList( - games: [testGame1, testGame2, testGame3], - ); - - final countBefore = await database.gameDao.getGameCount(); - expect(countBefore, 3); - - final result = await database.gameDao.deleteAllGames(); - expect(result, true); - - final countAfter = await database.gameDao.getGameCount(); - expect(countAfter, 0); - }); - - // Verifies that deleteAllGames returns false when no games exist. - test('deleteAllGames returns false when no games exist', () async { - final result = await database.gameDao.deleteAllGames(); - expect(result, false); - }); - - // Verifies that games with special characters (quotes, emojis) are stored correctly. - test('Game with special characters in name is stored correctly', () async { - final specialGame = Game( - name: 'Game\'s & "Special" ', - ruleset: Ruleset.multipleWinners, - description: 'Description with émojis 🎮🎲', - color: GameColor.purple, - icon: '', - ); - await database.gameDao.addGame(game: specialGame); - - final fetchedGame = await database.gameDao.getGameById( - gameId: specialGame.id, - ); - expect(fetchedGame.name, 'Game\'s & "Special" '); - expect(fetchedGame.description, 'Description with émojis 🎮🎲'); - }); - - // Verifies that games with empty string fields are stored and retrieved correctly. - test('Game with empty string fields is stored correctly', () async { - final emptyGame = Game( - name: '', - ruleset: Ruleset.singleWinner, - description: '', - icon: '', - color: GameColor.red, - ); - await database.gameDao.addGame(game: emptyGame); - - final fetchedGame = await database.gameDao.getGameById( - gameId: emptyGame.id, - ); - expect(fetchedGame.name, ''); - expect(fetchedGame.ruleset, Ruleset.singleWinner); - expect(fetchedGame.description, ''); - expect(fetchedGame.icon, ''); - }); - - // Verifies that games with very long strings (10000 chars) are handled correctly. - test('Game with very long strings is stored correctly', () async { - final longString = 'A' * 10000; - final longGame = Game( - name: longString, - description: longString, - ruleset: Ruleset.multipleWinners, - color: GameColor.yellow, - icon: '', - ); - await database.gameDao.addGame(game: longGame); - - final fetchedGame = await database.gameDao.getGameById( - gameId: longGame.id, - ); - expect(fetchedGame.name.length, 10000); - expect(fetchedGame.description.length, 10000); - expect(fetchedGame.ruleset, Ruleset.multipleWinners); - }); - - // Verifies that multiple sequential updates to the same game work correctly. - test('Multiple updates to the same game work correctly', () async { - await database.gameDao.addGame(game: testGame1); - - await database.gameDao.updateGameName( - gameId: testGame1.id, - newName: 'Updated Name', - ); - await database.gameDao.updateGameColor( - gameId: testGame1.id, - newColor: GameColor.teal, - ); - await database.gameDao.updateGameDescription( - gameId: testGame1.id, - newDescription: 'Updated Description', - ); - - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.name, 'Updated Name'); - expect(updatedGame.color, GameColor.teal); - expect(updatedGame.description, 'Updated Description'); - expect(updatedGame.ruleset, testGame1.ruleset); - expect(updatedGame.icon, testGame1.icon); + group('DELETE', () { + test('deleteGame() works correctly', () async { + await database.gameDao.addGame(game: testGame1); + + final deleted = await database.gameDao.deleteGame(gameId: testGame1.id); + expect(deleted, true); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + test('deleteGame() returns false for non-existent game', () async { + final deleted = await database.gameDao.deleteGame( + gameId: 'non-existent-id', + ); + expect(deleted, false); + }); + + test('deleteAllGames() removes all games', () async { + await database.gameDao.addGamesAsList( + games: [testGame1, testGame2, testGame3], + ); + + var count = await database.gameDao.getGameCount(); + expect(count, 3); + + final deleted = await database.gameDao.deleteAllGames(); + expect(deleted, true); + + count = await database.gameDao.getGameCount(); + expect(count, 0); + }); + + test('deleteAllGames() returns false when no games exist', () async { + final deleted = await database.gameDao.deleteAllGames(); + expect(deleted, false); + }); }); }); } -- 2.49.1 From 99b894c58091fc1ab3789357628bc3c1f2228626 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 24 Apr 2026 16:36:27 +0200 Subject: [PATCH 05/15] Updated score dao + tests --- lib/data/dao/score_entry_dao.dart | 72 ++-- .../match_view/match_result_view.dart | 4 +- test/db_tests/values/score_entry_test.dart | 378 ++++++++---------- 3 files changed, 215 insertions(+), 239 deletions(-) diff --git a/lib/data/dao/score_entry_dao.dart b/lib/data/dao/score_entry_dao.dart index 566b9d1..0d04e33 100644 --- a/lib/data/dao/score_entry_dao.dart +++ b/lib/data/dao/score_entry_dao.dart @@ -13,6 +13,8 @@ class ScoreEntryDao extends DatabaseAccessor with _$ScoreEntryDaoMixin { ScoreEntryDao(super.db); + /* Create */ + /// Adds a score entry to the database. Future addScore({ required String playerId, @@ -58,6 +60,8 @@ class ScoreEntryDao extends DatabaseAccessor }); } + /* Read */ + /// Retrieves the score for a specific round. Future getScore({ required String playerId, @@ -126,6 +130,34 @@ class ScoreEntryDao extends DatabaseAccessor ); } + /// 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(scoreEntryTable.roundNumber.max()); + } + + /// 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 getAllPlayerScoresInMatch( + playerId: playerId, + matchId: matchId, + ); + if (scores.isEmpty) return 0; + // Return the sum of all score changes + return scores.fold(0, (sum, element) => sum + element.change); + } + + /* Update */ + /// Updates a score entry. Future updateScore({ required String playerId, @@ -148,6 +180,8 @@ class ScoreEntryDao extends DatabaseAccessor return rowsAffected > 0; } + /* Delete */ + /// Deletes a score entry. Future deleteScore({ required String playerId, @@ -182,31 +216,7 @@ class ScoreEntryDao extends DatabaseAccessor return rowsAffected > 0; } - /// 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(scoreEntryTable.roundNumber.max()); - } - - /// 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 getAllPlayerScoresInMatch( - playerId: playerId, - matchId: matchId, - ); - if (scores.isEmpty) return 0; - // Return the sum of all score changes - return scores.fold(0, (sum, element) => sum + element.change); - } + /* Winner handling */ Future hasWinner({required String matchId}) async { return await getWinner(matchId: matchId) != null; @@ -275,12 +285,14 @@ class ScoreEntryDao extends DatabaseAccessor } } - Future hasLooser({required String matchId}) async { - return await getLooser(matchId: matchId) != null; + /* Loser handling */ + + Future hasLoser({required String matchId}) async { + return await getLoser(matchId: matchId) != null; } // Setting the looser for a game and clearing previous looser if exists. - Future setLooser({ + Future setLoser({ required String matchId, required String playerId, }) async { @@ -304,7 +316,7 @@ class ScoreEntryDao extends DatabaseAccessor /// Retrieves the looser of a match by looking for a score entry where score /// is 0. Returns `null` if no player found, else the first with the score. - Future getLooser({required String matchId}) async { + Future getLoser({required String matchId}) async { final query = select(scoreEntryTable).join([ innerJoin( @@ -332,7 +344,7 @@ class ScoreEntryDao extends DatabaseAccessor /// /// 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 { + Future removeLoser({required String matchId}) async { final scores = await getAllMatchScores(matchId: matchId); if (scores.length > 1) { 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 8b41920..1fd6780 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 @@ -242,9 +242,9 @@ class _MatchResultViewState extends State { /// Handles saving or removing the loser in the database. Future _handleLoser() async { if (_selectedPlayer == null) { - return await db.scoreEntryDao.removeLooser(matchId: widget.match.id); + return await db.scoreEntryDao.removeLoser(matchId: widget.match.id); } else { - return await db.scoreEntryDao.setLooser( + return await db.scoreEntryDao.setLoser( matchId: widget.match.id, playerId: _selectedPlayer!.id, ); diff --git a/test/db_tests/values/score_entry_test.dart b/test/db_tests/values/score_entry_test.dart index d550995..bb41a9a 100644 --- a/test/db_tests/values/score_entry_test.dart +++ b/test/db_tests/values/score_entry_test.dart @@ -17,6 +17,9 @@ void main() { late Game testGame; late Match testMatch1; late Match testMatch2; + ScoreEntry entryRound1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); + ScoreEntry entryRound2 = ScoreEntry(roundNumber: 2, score: 25, change: 15); + ScoreEntry entryRound3 = ScoreEntry(roundNumber: 3, score: 30, change: 5); final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -65,13 +68,12 @@ void main() { }); group('Score Tests', () { - group('Adding and Fetching scores', () { - test('Single Score', () async { - ScoreEntry entry = ScoreEntry(roundNumber: 1, score: 10, change: 10); + group('CREATE', () { + test('Adding and fetching single score works correctly', () async { await database.scoreEntryDao.addScore( playerId: testPlayer1.id, matchId: testMatch1.id, - entry: entry, + entry: entryRound1, ); final score = await database.scoreEntryDao.getScore( @@ -81,41 +83,37 @@ void main() { ); expect(score, isNotNull); - expect(score!.roundNumber, 1); - expect(score.score, 10); - expect(score.change, 10); + expect(score!.roundNumber, entryRound1.roundNumber); + expect(score.score, entryRound1.score); + expect(score.change, entryRound1.change); }); - 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), - ]; - + test('Adding and fetching single score works correctly', () async { await database.scoreEntryDao.addScoresAsList( - entrys: entryList, + entrys: [entryRound1, entryRound2, entryRound3], playerId: testPlayer1.id, matchId: testMatch1.id, ); - final scores = await database.scoreEntryDao.getAllPlayerScoresInMatch( + final entrys = await database.scoreEntryDao.getAllPlayerScoresInMatch( playerId: testPlayer1.id, matchId: testMatch1.id, ); - expect(scores, isNotNull); + expect(entrys, isNotEmpty); - // 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); + // Map for connecting fetched entry with expected entrys + final testScores = {1: entryRound1, 2: entryRound2, 3: entryRound3}; + + for (final entry in entrys) { + final testEntry = testScores[entry.roundNumber]!; + + expect(entry.roundNumber, testEntry.roundNumber); + expect(entry.score, testEntry.score); + expect(entry.change, testEntry.change); } }); - }); - group('Undesirable values', () { test('Score & Round can have negative values', () async { ScoreEntry entry = ScoreEntry(roundNumber: -2, score: -10, change: -10); await database.scoreEntryDao.addScore( @@ -155,6 +153,31 @@ void main() { expect(score.change, 0); }); + test('Adding the same score twice replaces the existing one', () async { + await database.scoreEntryDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entryRound1, + ); + await database.scoreEntryDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entryRound1, + ); + + final score = await database.scoreEntryDao.getScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(score, isNotNull); + expect(score!.score, entryRound1.score); + expect(score.change, entryRound1.change); + }); + }); + + group('READ', () { test('Getting score for a non-existent entities returns null', () async { var score = await database.scoreEntryDao.getScore( playerId: testPlayer1.id, @@ -201,10 +224,8 @@ void main() { expect(score, isNull); }); - }); - group('Scores in matches', () { - test('getAllMatchScores()', () async { + test('getAllMatchScores() works correctly', () 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); @@ -241,7 +262,7 @@ void main() { expect(scores.isEmpty, true); }); - test('getAllPlayerScoresInMatch()', () async { + test('getAllPlayerScoresInMatch() works correctly', () 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); @@ -315,28 +336,106 @@ void main() { expect(match2Scores[0].score, 50); expect(match2Scores[0].change, 50); }); + + test('getLatestRoundNumber() works correctly', () async { + var latestRound = await database.scoreEntryDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, isNull); + + await database.scoreEntryDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entryRound1, + ); + + latestRound = await database.scoreEntryDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, 1); + + await database.scoreEntryDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entry: entryRound2, + ); + + latestRound = await database.scoreEntryDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, 2); + }); + + test('getLatestRoundNumber() with non-consecutive rounds', () async { + await database.scoreEntryDao.addScoresAsList( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entrys: [entryRound1, entryRound3], + ); + + final latestRound = await database.scoreEntryDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + + expect(latestRound, 3); + }); + + test('getTotalScoreForPlayer() works correctly', () async { + var totalScore = await database.scoreEntryDao.getTotalScoreForPlayer( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + expect(totalScore, 0); + + await database.scoreEntryDao.addScoresAsList( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entrys: [entryRound1, entryRound2, entryRound3], + ); + + totalScore = await database.scoreEntryDao.getTotalScoreForPlayer( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + final expectedTotal = + entryRound1.change + entryRound2.change + entryRound3.change; + expect(totalScore, expectedTotal); + }); + + test('getTotalScoreForPlayer() ignores round score', () async { + await database.scoreEntryDao.addScoresAsList( + playerId: testPlayer1.id, + matchId: testMatch1.id, + entrys: [ + ScoreEntry(roundNumber: 2, score: 25, change: 25), + ScoreEntry(roundNumber: 1, score: 25, change: 10), + ScoreEntry(roundNumber: 3, score: 25, change: 25), + ], + ); + + final totalScore = await database.scoreEntryDao.getTotalScoreForPlayer( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + // Should return the sum of all changes + expect(totalScore, 60); + }); }); - 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.scoreEntryDao.addScore( + group('UPDATE', () { + test('updateScore() works correctly', () async { + await database.scoreEntryDao.addScoresAsList( playerId: testPlayer1.id, matchId: testMatch1.id, - entry: entry1, - ); - - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: entry2, + entrys: [entryRound1, entryRound2], ); + final newEntry = ScoreEntry(roundNumber: 2, score: 50, change: 40); final updated = await database.scoreEntryDao.updateScore( playerId: testPlayer1.id, matchId: testMatch1.id, - newEntry: ScoreEntry(roundNumber: 2, score: 50, change: 40), + newEntry: newEntry, ); expect(updated, true); @@ -348,23 +447,23 @@ void main() { ); expect(score, isNotNull); - expect(score!.score, 50); - expect(score.change, 40); + expect(score!.score, newEntry.score); + expect(score.change, newEntry.change); }); test('Updating a non-existent score returns false', () async { final updated = await database.scoreEntryDao.updateScore( playerId: testPlayer1.id, matchId: testMatch1.id, - newEntry: ScoreEntry(roundNumber: 1, score: 20, change: 20), + newEntry: entryRound1, ); expect(updated, false); }); }); - group('Deleting scores', () { - test('deleteScore() ', () async { + group('DELETE', () { + test('deleteScore() works correctly', () async { await database.scoreEntryDao.addScore( playerId: testPlayer1.id, matchId: testMatch1.id, @@ -399,20 +498,25 @@ void main() { }); test('deleteAllScoresForMatch() works correctly', () async { + final score1 = ScoreEntry(roundNumber: 1, score: 10, change: 10); await database.scoreEntryDao.addScore( playerId: testPlayer1.id, matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), + entry: score1, ); + + final score2 = ScoreEntry(roundNumber: 1, score: 20, change: 20); await database.scoreEntryDao.addScore( playerId: testPlayer2.id, matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 20, change: 20), + entry: score2, ); + + final score3 = ScoreEntry(roundNumber: 1, score: 15, change: 15); await database.scoreEntryDao.addScore( playerId: testPlayer1.id, matchId: testMatch2.id, - entry: ScoreEntry(roundNumber: 1, score: 15, change: 15), + entry: score3, ); final deleted = await database.scoreEntryDao.deleteAllScoresForMatch( @@ -433,22 +537,16 @@ void main() { }); test('deleteAllScoresForPlayerInMatch() works correctly', () async { - await database.scoreEntryDao.addScore( + await database.scoreEntryDao.addScoresAsList( playerId: testPlayer1.id, matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), - ); - - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 2, score: 15, change: 5), + entrys: [entryRound1, entryRound2], ); await database.scoreEntryDao.addScore( playerId: testPlayer2.id, matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 6, change: 6), + entry: entryRound1, ); final deleted = await database.scoreEntryDao @@ -475,141 +573,7 @@ void main() { }); }); - group('Score Aggregations & Edge Cases', () { - test('getLatestRoundNumber()', () async { - var latestRound = await database.scoreEntryDao.getLatestRoundNumber( - matchId: testMatch1.id, - ); - expect(latestRound, isNull); - - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), - ); - - latestRound = await database.scoreEntryDao.getLatestRoundNumber( - matchId: testMatch1.id, - ); - expect(latestRound, 1); - - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 5, score: 50, change: 40), - ); - - latestRound = await database.scoreEntryDao.getLatestRoundNumber( - matchId: testMatch1.id, - ); - expect(latestRound, 5); - }); - - test('getLatestRoundNumber() with non-consecutive rounds', () async { - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), - ); - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 5, score: 50, change: 40), - ); - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 3, score: 30, change: 20), - ); - - final latestRound = await database.scoreEntryDao.getLatestRoundNumber( - matchId: testMatch1.id, - ); - - expect(latestRound, 5); - }); - - test('getTotalScoreForPlayer()', () async { - var totalScore = await database.scoreEntryDao.getTotalScoreForPlayer( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); - expect(totalScore, 0); - - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), - ); - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 2, score: 25, change: 15), - ); - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 3, score: 40, change: 15), - ); - - totalScore = await database.scoreEntryDao.getTotalScoreForPlayer( - playerId: testPlayer1.id, - matchId: testMatch1.id, - ); - expect(totalScore, 40); - }); - - test('getTotalScoreForPlayer() ignores round score', () async { - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 2, score: 25, change: 25), - ); - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 25, change: 10), - ); - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 3, score: 25, change: 25), - ); - - final totalScore = await database.scoreEntryDao.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.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 10, change: 10), - ); - await database.scoreEntryDao.addScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - entry: ScoreEntry(roundNumber: 1, score: 20, change: 20), - ); - - final score = await database.scoreEntryDao.getScore( - playerId: testPlayer1.id, - matchId: testMatch1.id, - roundNumber: 1, - ); - - expect(score, isNotNull); - expect(score!.score, 20); - expect(score.change, 20); - }); - }); - - group('Handling Winner', () { + group('WINNER', () { test('hasWinner() works correctly', () async { var hasWinner = await database.scoreEntryDao.hasWinner( matchId: testMatch1.id, @@ -667,58 +631,58 @@ void main() { }); }); - group('Handling Looser', () { - test('hasLooser() works correctly', () async { - var hasLooser = await database.scoreEntryDao.hasLooser( + group('LOSER', () { + test('hasLoser() works correctly', () async { + var hasLooser = await database.scoreEntryDao.hasLoser( matchId: testMatch1.id, ); expect(hasLooser, false); - await database.scoreEntryDao.setLooser( + await database.scoreEntryDao.setLoser( playerId: testPlayer1.id, matchId: testMatch1.id, ); - hasLooser = await database.scoreEntryDao.hasLooser( + hasLooser = await database.scoreEntryDao.hasLoser( matchId: testMatch1.id, ); expect(hasLooser, true); }); - test('getLooser() returns correct winner', () async { - var looser = await database.scoreEntryDao.getLooser( + test('getLoser() returns correct winner', () async { + var looser = await database.scoreEntryDao.getLoser( matchId: testMatch1.id, ); expect(looser, isNull); - await database.scoreEntryDao.setLooser( + await database.scoreEntryDao.setLoser( playerId: testPlayer1.id, matchId: testMatch1.id, ); - looser = await database.scoreEntryDao.getLooser(matchId: testMatch1.id); + looser = await database.scoreEntryDao.getLoser(matchId: testMatch1.id); expect(looser, isNotNull); expect(looser!.id, testPlayer1.id); }); - test('removeLooser() works correctly', () async { - var removed = await database.scoreEntryDao.removeLooser( + test('removeLoser() works correctly', () async { + var removed = await database.scoreEntryDao.removeLoser( matchId: testMatch1.id, ); expect(removed, false); - await database.scoreEntryDao.setLooser( + await database.scoreEntryDao.setLoser( playerId: testPlayer1.id, matchId: testMatch1.id, ); - removed = await database.scoreEntryDao.removeLooser( + removed = await database.scoreEntryDao.removeLoser( matchId: testMatch1.id, ); expect(removed, true); - var looser = await database.scoreEntryDao.getLooser( + var looser = await database.scoreEntryDao.getLoser( matchId: testMatch1.id, ); expect(looser, isNull); -- 2.49.1 From 785873d3c88fee9f74e7fbd711a2905db4889f38 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 24 Apr 2026 16:36:36 +0200 Subject: [PATCH 06/15] Fixed error in game tests --- test/db_tests/entities/game_test.dart | 46 +++++++++++++-------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/test/db_tests/entities/game_test.dart b/test/db_tests/entities/game_test.dart index 9f94910..f3e99b6 100644 --- a/test/db_tests/entities/game_test.dart +++ b/test/db_tests/entities/game_test.dart @@ -24,6 +24,7 @@ void main() { withClock(fakeClock, () { testGame1 = Game( + id: 'game1', name: 'Chess', ruleset: Ruleset.singleWinner, description: 'A classic strategy game', @@ -142,12 +143,12 @@ void main() { await database.gameDao.addGame(game: testGame1); final game = await database.gameDao.getGameById(gameId: testGame1.id); - expect(game.id, testGame2.id); - expect(game.name, testGame2.name); - expect(game.ruleset, testGame2.ruleset); - expect(game.description, testGame2.description); - expect(game.color, testGame2.color); - expect(game.icon, testGame2.icon); + expect(game.id, testGame1.id); + expect(game.name, testGame1.name); + expect(game.ruleset, testGame1.ruleset); + expect(game.description, testGame1.description); + expect(game.color, testGame1.color); + expect(game.icon, testGame1.icon); }); test('getGameById() throws exception for non-existent game', () async { @@ -190,7 +191,7 @@ void main() { }); group('UPDATE', () { - test('updateGameName() updates the name correctly', () async { + test('updateGameName() works correctly', () async { await database.gameDao.addGame(game: testGame1); const newName = 'New name'; @@ -217,7 +218,7 @@ void main() { expect(allGames, isEmpty); }); - test('updateGameRuleset() updates the ruleset correctly', () async { + test('updateGameRuleset() works correctly', () async { await database.gameDao.addGame(game: testGame1); const ruleset = Ruleset.highestScore; @@ -244,24 +245,21 @@ void main() { expect(allGames, isEmpty); }); - test( - 'updateGameDescription() updates the description correctly', - () async { - await database.gameDao.addGame(game: testGame1); - const newDescription = 'New description'; + test('updateGameDescription() works correctly', () async { + await database.gameDao.addGame(game: testGame1); + const newDescription = 'New description'; - final updated = await database.gameDao.updateGameDescription( - gameId: testGame1.id, - newDescription: newDescription, - ); - expect(updated, true); + final updated = await database.gameDao.updateGameDescription( + gameId: testGame1.id, + newDescription: newDescription, + ); + expect(updated, true); - final updatedGame = await database.gameDao.getGameById( - gameId: testGame1.id, - ); - expect(updatedGame.description, newDescription); - }, - ); + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.description, newDescription); + }); test( 'updateGameDescription() does nothing for non-existent game', -- 2.49.1 From be6b968a4321325330a5e01a172a12973cdf1401 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 1 May 2026 11:57:03 +0200 Subject: [PATCH 07/15] Updated match dao + tests --- lib/data/dao/match_dao.dart | 242 +++++----- lib/services/data_transfer_service.dart | 2 +- test/db_tests/aggregates/match_test.dart | 541 ++++++++++++++--------- 3 files changed, 438 insertions(+), 347 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 93df7d7..3df8b9d 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -15,74 +15,13 @@ part 'match_dao.g.dart'; class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { MatchDao(super.db); - /// Retrieves all matches from the database. - Future> getAllMatches() async { - final query = select(matchTable); - final result = await query.get(); - - return Future.wait( - result.map((row) async { - final game = await db.gameDao.getGameById(gameId: row.gameId); - Group? group; - if (row.groupId != null) { - group = await db.groupDao.getGroupById(groupId: row.groupId!); - } - final players = - await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; - - final scores = await db.scoreEntryDao.getAllMatchScores( - matchId: row.id, - ); - - return Match( - id: row.id, - name: row.name, - game: game, - group: group, - players: players, - notes: row.notes ?? '', - createdAt: row.createdAt, - endedAt: row.endedAt, - scores: scores, - ); - }), - ); - } - - /// Retrieves a [Match] by its [matchId]. - Future getMatchById({required String matchId}) async { - final query = select(matchTable)..where((g) => g.id.equals(matchId)); - final result = await query.getSingle(); - - final game = await db.gameDao.getGameById(gameId: result.gameId); - - Group? group; - if (result.groupId != null) { - group = await db.groupDao.getGroupById(groupId: result.groupId!); - } - - final players = - await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? []; - - final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId); - - return Match( - id: result.id, - name: result.name, - game: game, - group: group, - players: players, - notes: result.notes ?? '', - createdAt: result.createdAt, - endedAt: result.endedAt, - scores: scores, - ); - } + /* Create */ /// Adds a new [Match] to the database. Also adds players associations. /// This method assumes that the game and group (if any) are already present /// in the database. - Future addMatch({required Match match}) async { + Future addMatch({required Match match}) async { + if (await matchExists(matchId: match.id)) return false; await db.transaction(() async { await into(matchTable).insert( MatchTableCompanion.insert( @@ -114,15 +53,17 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } } + print('Return true'); }); + return true; } /// Adds multiple [Match]es to the database in a batch operation. /// Also adds associated players and groups if they exist. /// If the [matches] list is empty, the method returns immediately. /// This method should only be used to import matches from a different device. - Future addMatchAsList({required List matches}) async { - if (matches.isEmpty) return; + Future addMatchesAsList({required List matches}) async { + if (matches.isEmpty) return false; await db.transaction(() async { // Add all games first (deduplicated) final uniqueGames = {}; @@ -280,14 +221,17 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { } }); }); + return true; } - /// Deletes the match with the given [matchId] from the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteMatch({required String matchId}) async { - final query = delete(matchTable)..where((g) => g.id.equals(matchId)); - final rowsAffected = await query.go(); - return rowsAffected > 0; + /* Read */ + + /// Checks if a match with the given [matchId] exists in the database. + /// Returns `true` if the match exists, otherwise `false`. + Future matchExists({required String matchId}) async { + final query = select(matchTable)..where((g) => g.id.equals(matchId)); + final result = await query.getSingleOrNull(); + return result != null; } /// Retrieves the number of matches in the database. @@ -299,6 +243,70 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { return count ?? 0; } + /// Retrieves all matches from the database. + Future> getAllMatches() async { + final query = select(matchTable); + final result = await query.get(); + + return Future.wait( + result.map((row) async { + final game = await db.gameDao.getGameById(gameId: row.gameId); + Group? group; + if (row.groupId != null) { + group = await db.groupDao.getGroupById(groupId: row.groupId!); + } + final players = + await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; + + final scores = await db.scoreEntryDao.getAllMatchScores( + matchId: row.id, + ); + + return Match( + id: row.id, + name: row.name, + game: game, + group: group, + players: players, + notes: row.notes ?? '', + createdAt: row.createdAt, + endedAt: row.endedAt, + scores: scores, + ); + }), + ); + } + + /// Retrieves a [Match] by its [matchId]. + Future getMatchById({required String matchId}) async { + final query = select(matchTable)..where((g) => g.id.equals(matchId)); + final result = await query.getSingle(); + + final game = await db.gameDao.getGameById(gameId: result.gameId); + + Group? group; + if (result.groupId != null) { + group = await db.groupDao.getGroupById(groupId: result.groupId!); + } + + final players = + await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? []; + + final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId); + + return Match( + id: result.id, + name: result.name, + game: game, + group: group, + players: players, + notes: result.notes ?? '', + createdAt: result.createdAt, + endedAt: result.endedAt, + scores: scores, + ); + } + /// Retrieves all matches associated with the given [groupId]. /// Queries the database directly, filtering by [groupId]. Future> getGroupMatches({required String groupId}) async { @@ -325,34 +333,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } - /// Checks if a match with the given [matchId] exists in the database. - /// Returns `true` if the match exists, otherwise `false`. - Future matchExists({required String matchId}) async { - final query = select(matchTable)..where((g) => g.id.equals(matchId)); - final result = await query.getSingleOrNull(); - return result != null; - } - - /// Deletes all matches from the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteAllMatches() async { - final query = delete(matchTable); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - - /// Updates the notes of the match with the given [matchId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future updateMatchNotes({ - required String matchId, - required String? notes, - }) async { - final query = update(matchTable)..where((g) => g.id.equals(matchId)); - final rowsAffected = await query.write( - MatchTableCompanion(notes: Value(notes)), - ); - return rowsAffected > 0; - } + /* Update */ /// Changes the name of the match with the given [matchId] to [newName]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. @@ -367,19 +348,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { return rowsAffected > 0; } - /// Updates the game of the match with the given [matchId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future updateMatchGame({ - required String matchId, - required String gameId, - }) async { - final query = update(matchTable)..where((g) => g.id.equals(matchId)); - final rowsAffected = await query.write( - MatchTableCompanion(gameId: Value(gameId)), - ); - return rowsAffected > 0; - } - /// Updates the group of the match with the given [matchId]. /// Replaces the existing group association with the new group specified by [newGroupId]. /// Pass null to remove the group association. @@ -395,6 +363,19 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { return rowsAffected > 0; } + /// Updates the notes of the match with the given [matchId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updateMatchNotes({ + required String matchId, + required String notes, + }) async { + final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.write( + MatchTableCompanion(notes: Value(notes)), + ); + return rowsAffected > 0; + } + /// Removes the group association of the match with the given [matchId]. /// Sets the groupId to null. /// Returns `true` if more than 0 rows were affected, otherwise `false`. @@ -406,25 +387,12 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { return rowsAffected > 0; } - /// Updates the createdAt timestamp of the match with the given [matchId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future updateMatchCreatedAt({ - required String matchId, - required DateTime createdAt, - }) async { - final query = update(matchTable)..where((g) => g.id.equals(matchId)); - final rowsAffected = await query.write( - MatchTableCompanion(createdAt: Value(createdAt)), - ); - return rowsAffected > 0; - } - /// Updates the endedAt timestamp of the match with the given [matchId]. /// Pass null to remove the ended time (mark match as ongoing). /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updateMatchEndedAt({ required String matchId, - required DateTime? endedAt, + required DateTime endedAt, }) async { final query = update(matchTable)..where((g) => g.id.equals(matchId)); final rowsAffected = await query.write( @@ -436,7 +404,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /// Replaces all players in a match with the provided list of players. /// Removes all existing players from the match and adds the new players. /// Also adds any new players to the player table if they don't exist. - Future replaceMatchPlayers({ + Future updateMatchPlayers({ required String matchId, required List newPlayers, }) async { @@ -466,4 +434,22 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); }); } + + /* Delete */ + + /// Deletes the match with the given [matchId] from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteMatch({required String matchId}) async { + final query = delete(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /// Deletes all matches from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllMatches() async { + final query = delete(matchTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } } diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index daf4768..e47b220 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -143,7 +143,7 @@ class DataTransferService { 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 db.matchDao.addMatchesAsList(matches: importedMatches); } /* Parsing Methods */ diff --git a/test/db_tests/aggregates/match_test.dart b/test/db_tests/aggregates/match_test.dart index 3305b9a..a056b2c 100644 --- a/test/db_tests/aggregates/match_test.dart +++ b/test/db_tests/aggregates/match_test.dart @@ -101,212 +101,221 @@ void main() { }); group('Match Tests', () { - // Verifies that a single match can be added and retrieved with all fields, group, and players intact. - test('Adding and fetching single match works correctly', () async { - await database.matchDao.addMatch(match: testMatch1); + group('CREATE', () { + test('Adding and fetching single match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - final result = await database.matchDao.getMatchById( - matchId: testMatch1.id, - ); + final result = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); - expect(result.id, testMatch1.id); - expect(result.name, testMatch1.name); - expect(result.createdAt, testMatch1.createdAt); + expect(result.id, testMatch1.id); + expect(result.name, testMatch1.name); + expect(result.createdAt, testMatch1.createdAt); - if (result.group != null) { - expect(result.group!.members.length, testGroup1.members.length); + if (result.group != null) { + expect(result.group!.members.length, testGroup1.members.length); - for (int i = 0; i < testGroup1.members.length; i++) { - expect(result.group!.members[i].id, testGroup1.members[i].id); - expect(result.group!.members[i].name, testGroup1.members[i].name); - } - } else { - fail('Group is null'); - } - expect(result.players.length, testMatch1.players.length); - - for (int i = 0; i < testMatch1.players.length; i++) { - expect(result.players[i].id, testMatch1.players[i].id); - expect(result.players[i].name, testMatch1.players[i].name); - expect(result.players[i].createdAt, testMatch1.players[i].createdAt); - } - }); - - // Verifies that multiple matches can be added and retrieved with correct groups and players. - test('Adding and fetching multiple matches works correctly', () async { - await database.matchDao.addMatchAsList( - matches: [ - testMatch1, - testMatch2, - testMatchOnlyGroup, - testMatchOnlyPlayers, - ], - ); - - final allMatches = await database.matchDao.getAllMatches(); - expect(allMatches.length, 4); - - final testMatches = { - testMatch1.id: testMatch1, - testMatch2.id: testMatch2, - testMatchOnlyGroup.id: testMatchOnlyGroup, - testMatchOnlyPlayers.id: testMatchOnlyPlayers, - }; - - for (final match in allMatches) { - final testMatch = testMatches[match.id]!; - - // Match-Checks - expect(match.id, testMatch.id); - expect(match.name, testMatch.name); - expect(match.createdAt, testMatch.createdAt); - - // Group-Checks - if (testMatch.group != null) { - expect(match.group!.id, testMatch.group!.id); - expect(match.group!.name, testMatch.group!.name); - expect(match.group!.createdAt, testMatch.group!.createdAt); - - // Group Members-Checks - expect(match.group!.members.length, testMatch.group!.members.length); - for (int i = 0; i < testMatch.group!.members.length; i++) { - expect(match.group!.members[i].id, testMatch.group!.members[i].id); - expect( - match.group!.members[i].name, - testMatch.group!.members[i].name, - ); - expect( - match.group!.members[i].createdAt, - testMatch.group!.members[i].createdAt, - ); + for (int i = 0; i < testGroup1.members.length; i++) { + expect(result.group!.members[i].id, testGroup1.members[i].id); + expect(result.group!.members[i].name, testGroup1.members[i].name); } } else { - expect(match.group, null); + fail('Group is null'); } + expect(result.players.length, testMatch1.players.length); - // Players-Checks - expect(match.players.length, testMatch.players.length); - for (int i = 0; i < testMatch.players.length; i++) { - expect(match.players[i].id, testMatch.players[i].id); - expect(match.players[i].name, testMatch.players[i].name); - expect(match.players[i].createdAt, testMatch.players[i].createdAt); + for (int i = 0; i < testMatch1.players.length; i++) { + expect(result.players[i].id, testMatch1.players[i].id); + expect(result.players[i].name, testMatch1.players[i].name); + expect(result.players[i].createdAt, testMatch1.players[i].createdAt); } - } + }); + + test('Adding and fetching multiple matches works correctly', () async { + await database.matchDao.addMatchesAsList( + matches: [ + testMatch1, + testMatch2, + testMatchOnlyGroup, + testMatchOnlyPlayers, + ], + ); + + final allMatches = await database.matchDao.getAllMatches(); + expect(allMatches.length, 4); + + final testMatches = { + testMatch1.id: testMatch1, + testMatch2.id: testMatch2, + testMatchOnlyGroup.id: testMatchOnlyGroup, + testMatchOnlyPlayers.id: testMatchOnlyPlayers, + }; + + for (final match in allMatches) { + final testMatch = testMatches[match.id]!; + + // Match-Checks + expect(match.id, testMatch.id); + expect(match.name, testMatch.name); + expect(match.createdAt, testMatch.createdAt); + + // Group-Checks + if (testMatch.group != null) { + expect(match.group!.id, testMatch.group!.id); + expect(match.group!.name, testMatch.group!.name); + expect(match.group!.createdAt, testMatch.group!.createdAt); + + // Group Members-Checks + expect( + match.group!.members.length, + testMatch.group!.members.length, + ); + for (int i = 0; i < testMatch.group!.members.length; i++) { + expect( + match.group!.members[i].id, + testMatch.group!.members[i].id, + ); + expect( + match.group!.members[i].name, + testMatch.group!.members[i].name, + ); + expect( + match.group!.members[i].createdAt, + testMatch.group!.members[i].createdAt, + ); + } + } else { + expect(match.group, null); + } + + // Players-Checks + expect(match.players.length, testMatch.players.length); + for (int i = 0; i < testMatch.players.length; i++) { + expect(match.players[i].id, testMatch.players[i].id); + expect(match.players[i].name, testMatch.players[i].name); + expect(match.players[i].createdAt, testMatch.players[i].createdAt); + } + } + }); + + test('addMatch() ignores duplicate games', () async { + var added = await database.matchDao.addMatch(match: testMatch1); + expect(added, isTrue); + + added = await database.matchDao.addMatch(match: testMatch1); + expect(added, isFalse); + + final matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); + }); + + test('addMatchesAsList() returns isFalse for empty list', () async { + var added = await database.matchDao.addMatchesAsList(matches: []); + expect(added, isFalse); + }); + + test('addMatchesAsList() ignores duplicate games', () async { + final added = await database.matchDao.addMatchesAsList( + matches: [testMatch1, testMatch2, testMatch1], + ); + expect(added, isTrue); + + final matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 2); + }); }); - // Verifies that adding the same match twice does not create duplicates. - test('Adding the same match twice does not create duplicates', () async { - await database.matchDao.addMatch(match: testMatch1); - await database.matchDao.addMatch(match: testMatch1); + group('READ', () { + test('matchExists() works correctly', () async { + var matchExists = await database.matchDao.matchExists( + matchId: testMatch1.id, + ); + expect(matchExists, isFalse); - final matchCount = await database.matchDao.getMatchCount(); - expect(matchCount, 1); + await database.matchDao.addMatch(match: testMatch1); + + matchExists = await database.matchDao.matchExists( + matchId: testMatch1.id, + ); + expect(matchExists, isTrue); + }); + + test('getGroupMatches() works correctly', () async { + var matches = await database.matchDao.getGroupMatches( + groupId: 'non-existing-id', + ); + + expect(matches, isEmpty); + + await database.matchDao.addMatch(match: testMatch1); + matches = await database.matchDao.getGroupMatches( + groupId: testGroup1.id, + ); + expect(matches, isNotEmpty); + + final match = matches.first; + expect(match.id, testMatch1.id); + expect(match.group, isNotNull); + expect(match.group!.id, testGroup1.id); + }); }); - // Verifies that matchExists returns correct boolean based on match presence. - test('Match existence check works correctly', () async { - var matchExists = await database.matchDao.matchExists( - matchId: testMatch1.id, - ); - expect(matchExists, false); + group('UPDATE', () { + test('updateMatchName() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - await database.matchDao.addMatch(match: testMatch1); + const newName = 'New name'; + await database.matchDao.updateMatchName( + matchId: testMatch1.id, + newName: newName, + ); - matchExists = await database.matchDao.matchExists(matchId: testMatch1.id); - expect(matchExists, true); - }); + final fetchedMatch = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); + expect(fetchedMatch.name, newName); + }); - // Verifies that deleteMatch removes the match and returns true. - test('Deleting a match works correctly', () async { - await database.matchDao.addMatch(match: testMatch1); + test('updateMatchName() does nothing for non-existent match', () async { + final updated = await database.matchDao.updateMatchName( + matchId: 'non-existing-id', + newName: 'New Name', + ); + expect(updated, isFalse); - final matchDeleted = await database.matchDao.deleteMatch( - matchId: testMatch1.id, - ); - expect(matchDeleted, true); + final allMatches = await database.matchDao.getAllMatches(); + expect(allMatches, isEmpty); + }); - final matchExists = await database.matchDao.matchExists( - matchId: testMatch1.id, - ); - expect(matchExists, false); - }); + test('updateMatchGroup() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.groupDao.addGroup(group: testGroup2); - // Verifies that getMatchCount returns correct count through add/delete operations. - test('Getting the match count works correctly', () async { - var matchCount = await database.matchDao.getMatchCount(); - expect(matchCount, 0); + await database.matchDao.updateMatchGroup( + matchId: testMatch1.id, + newGroupId: testGroup2.id, + ); - await database.matchDao.addMatch(match: testMatch1); + final fetchedMatch = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); + expect(fetchedMatch.group?.id, testGroup2.id); + }); - matchCount = await database.matchDao.getMatchCount(); - expect(matchCount, 1); + test('updateMatchGroup() does nothing for non-existent match', () async { + final updated = await database.matchDao.updateMatchGroup( + matchId: 'non-existing-id', + newGroupId: 'group-id', + ); + expect(updated, isFalse); - await database.matchDao.addMatch(match: testMatch2); + final allMatches = await database.matchDao.getAllMatches(); + expect(allMatches, isEmpty); + }); - matchCount = await database.matchDao.getMatchCount(); - expect(matchCount, 2); - - await database.matchDao.deleteMatch(matchId: testMatch1.id); - - matchCount = await database.matchDao.getMatchCount(); - expect(matchCount, 1); - - await database.matchDao.deleteMatch(matchId: testMatch2.id); - - matchCount = await database.matchDao.getMatchCount(); - expect(matchCount, 0); - }); - - // Verifies that updateMatchName correctly updates only the name field. - test('Renaming a match works correctly', () async { - await database.matchDao.addMatch(match: testMatch1); - - var fetchedMatch = await database.matchDao.getMatchById( - matchId: testMatch1.id, - ); - expect(fetchedMatch.name, testMatch1.name); - - const newName = 'Updated Match Name'; - await database.matchDao.updateMatchName( - matchId: testMatch1.id, - newName: newName, - ); - - fetchedMatch = await database.matchDao.getMatchById( - matchId: testMatch1.id, - ); - expect(fetchedMatch.name, newName); - }); - - test('Fetching a winner works correctly', () async { - await database.matchDao.addMatch(match: testMatch1); - - var fetchedMatch = await database.matchDao.getMatchById( - matchId: testMatch1.id, - ); - - expect(fetchedMatch.mvp, isNotNull); - expect(fetchedMatch.mvp.first.id, testPlayer4.id); - }); - - test('Setting a winner works correctly', () async { - await database.matchDao.addMatch(match: testMatch1); - - await database.scoreEntryDao.setWinner( - matchId: testMatch1.id, - playerId: testPlayer5.id, - ); - - final fetchedMatch = await database.matchDao.getMatchById( - matchId: testMatch1.id, - ); - expect(fetchedMatch.mvp, isNotNull); - expect(fetchedMatch.mvp.first.id, testPlayer5.id); - }); - - test( - 'removeMatchGroup removes group from match with existing group', - () async { + test('removeMatchGroup() works correctly', () async { + expect(testMatch1.group, isNotNull); await database.matchDao.addMatch(match: testMatch1); final removed = await database.matchDao.removeMatchGroup( @@ -318,53 +327,149 @@ void main() { matchId: testMatch1.id, ); expect(updatedMatch.group, null); - expect(updatedMatch.game.id, testMatch1.game.id); - expect(updatedMatch.name, testMatch1.name); - expect(updatedMatch.notes, testMatch1.notes); - }, - ); + }); - test( - 'removeMatchGroup on match that already has no group still succeeds', - () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); + test( + 'removeMatchGroup() on match that already has no group still succeeds', + () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); - final removed = await database.matchDao.removeMatchGroup( - matchId: testMatchOnlyPlayers.id, - ); - expect(removed, isTrue); + final removed = await database.matchDao.removeMatchGroup( + matchId: testMatchOnlyPlayers.id, + ); + expect(removed, isTrue); - final updatedMatch = await database.matchDao.getMatchById( - matchId: testMatchOnlyPlayers.id, - ); - expect(updatedMatch.group, null); - }, - ); - - test('removeMatchGroup on non-existing match returns false', () async { - final removed = await database.matchDao.removeMatchGroup( - matchId: 'non-existing-id', + final updatedMatch = await database.matchDao.getMatchById( + matchId: testMatchOnlyPlayers.id, + ); + expect(updatedMatch.group, null); + }, ); - expect(removed, isFalse); + + test( + 'removeMatchGroup() on non-existing match returns isFalse', + () async { + final removed = await database.matchDao.removeMatchGroup( + matchId: 'non-existing-id', + ); + expect(removed, isFalse); + }, + ); + + test('updateMatchNotes() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + + const newName = 'New name'; + await database.matchDao.updateMatchName( + matchId: testMatch1.id, + newName: newName, + ); + + final fetchedMatch = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); + expect(fetchedMatch.name, newName); + }); + + test('updateMatchNotes() does nothing for non-existent game', () async { + final updated = await database.matchDao.updateMatchNotes( + matchId: 'non-existing-id', + notes: 'New notes', + ); + expect(updated, isFalse); + + final allMatches = await database.matchDao.getAllMatches(); + expect(allMatches, isEmpty); + }); + + test('updateMatchEndedAt() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + + DateTime newEndedAt = DateTime(2030, 1, 1, 12, 0, 0); + print(newEndedAt); + await database.matchDao.updateMatchEndedAt( + matchId: testMatch1.id, + endedAt: newEndedAt, + ); + + final fetchedMatch = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); + expect(fetchedMatch.endedAt, newEndedAt); + }); + + test('updateMatchEndedAt() does nothing for non-existent game', () async { + final updated = await database.matchDao.updateMatchEndedAt( + matchId: 'non-existing-id', + endedAt: DateTime.now(), + ); + expect(updated, isFalse); + + final allMatches = await database.matchDao.getAllMatches(); + expect(allMatches, isEmpty); + }); + + test('Getting the match count works correctly', () async { + var matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 0); + + await database.matchDao.addMatch(match: testMatch1); + + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); + + await database.matchDao.addMatch(match: testMatch2); + + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 2); + + await database.matchDao.deleteMatch(matchId: testMatch1.id); + + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); + + await database.matchDao.deleteMatch(matchId: testMatch2.id); + + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 0); + }); }); - test('Fetching all matches related to a group', () async { - var matches = await database.matchDao.getGroupMatches( - groupId: 'non-existing-id', - ); + group('DELETE', () { + test('deleteMatch() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - expect(matches, isEmpty); + var deleted = await database.matchDao.deleteMatch( + matchId: testMatch1.id, + ); + expect(deleted, isTrue); - await database.matchDao.addMatch(match: testMatch1); + final matchExists = await database.matchDao.matchExists( + matchId: testMatch1.id, + ); + expect(matchExists, isFalse); - matches = await database.matchDao.getGroupMatches(groupId: testGroup1.id); + deleted = await database.matchDao.deleteMatch(matchId: testMatch1.id); + expect(deleted, isFalse); + }); - expect(matches, isNotEmpty); + test('deleteAllMatches() works correctly', () async { + await database.matchDao.addMatchesAsList( + matches: [testMatch1, testMatch2, testMatchOnlyPlayers], + ); - final match = matches.first; - expect(match.id, testMatch1.id); - expect(match.group, isNotNull); - expect(match.group!.id, testGroup1.id); + var count = await database.matchDao.getMatchCount(); + expect(count, 3); + + var deleted = await database.matchDao.deleteAllMatches(); + expect(deleted, isTrue); + + count = await database.matchDao.getMatchCount(); + expect(count, 0); + + deleted = await database.matchDao.deleteAllMatches(); + expect(deleted, isFalse); + }); }); }); } -- 2.49.1 From f49440367cb3b5d33a780ac3c4c9ebf8c92104ec Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 1 May 2026 14:31:15 +0200 Subject: [PATCH 08/15] Finalized and updated team implementation, updated team dao, tests, and ex-/import --- lib/data/dao/match_dao.dart | 50 +- lib/data/dao/team_dao.dart | 184 +++-- lib/data/dao/team_dao.g.dart | 19 + lib/data/db/database.g.dart | 9 +- lib/data/db/tables/player_match_table.dart | 4 +- lib/data/models/match.dart | 5 + lib/services/data_transfer_service.dart | 14 +- test/db_tests/aggregates/team_test.dart | 704 +++++++----------- test/services/data_transfer_service_test.dart | 44 +- 9 files changed, 480 insertions(+), 553 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 3df8b9d..faa0227 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -8,6 +8,7 @@ import 'package:tallee/data/models/game.dart'; import 'package:tallee/data/models/group.dart'; import 'package:tallee/data/models/match.dart'; import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/team.dart'; part 'match_dao.g.dart'; @@ -17,7 +18,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /* Create */ - /// Adds a new [Match] to the database. Also adds players associations. + /// Adds a new [Match] to the database. Also adds players associations and teams. /// This method assumes that the game and group (if any) are already present /// in the database. Future addMatch({required Match match}) async { @@ -36,6 +37,11 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { mode: InsertMode.insertOrReplace, ); + // Add teams + if (match.teams != null && match.teams!.isNotEmpty) { + await db.teamDao.addTeamsAsList(teams: match.teams!, matchId: match.id); + } + for (final p in match.players) { await db.playerMatchDao.addPlayerToMatch( matchId: match.id, @@ -53,7 +59,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } } - print('Return true'); }); return true; } @@ -220,6 +225,16 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { } } }); + + // Add teams for matches + for (final match in matches) { + if (match.teams != null && match.teams!.isNotEmpty) { + await db.teamDao.addTeamsAsList( + teams: match.teams!, + matchId: match.id, + ); + } + } }); return true; } @@ -262,12 +277,15 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { matchId: row.id, ); + final teams = await _getMatchTeams(matchId: row.id); + return Match( id: row.id, name: row.name, game: game, group: group, players: players, + teams: teams.isEmpty ? null : teams, notes: row.notes ?? '', createdAt: row.createdAt, endedAt: row.endedAt, @@ -294,12 +312,15 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId); + final teams = await _getMatchTeams(matchId: matchId); + return Match( id: result.id, name: result.name, game: game, group: group, players: players, + teams: teams.isEmpty ? null : teams, notes: result.notes ?? '', createdAt: result.createdAt, endedAt: result.endedAt, @@ -319,12 +340,14 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { final group = await db.groupDao.getGroupById(groupId: groupId); final players = await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; + final teams = await _getMatchTeams(matchId: row.id); return Match( id: row.id, name: row.name, game: game, group: group, players: players, + teams: teams.isEmpty ? null : teams, notes: row.notes ?? '', createdAt: row.createdAt, endedAt: row.endedAt, @@ -333,6 +356,29 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } + /// Helper method to retrieve teams for a specific match + Future> _getMatchTeams({required String matchId}) async { + // Get all unique team IDs from PlayerMatchTable for this match + final playerMatchQuery = select(db.playerMatchTable) + ..where((pm) => pm.matchId.equals(matchId) & pm.teamId.isNotNull()); + final playerMatches = await playerMatchQuery.get(); + + if (playerMatches.isEmpty) return []; + + final teamIds = playerMatches + .map((pm) => pm.teamId) + .whereType() + .toSet() + .toList(); + + // Fetch all teams + final teams = await Future.wait( + teamIds.map((teamId) => db.teamDao.getTeamById(teamId: teamId)), + ); + + return teams; + } + /* Update */ /// Changes the name of the match with the given [matchId] to [newName]. diff --git a/lib/data/dao/team_dao.dart b/lib/data/dao/team_dao.dart index 01dc724..708b475 100644 --- a/lib/data/dao/team_dao.dart +++ b/lib/data/dao/team_dao.dart @@ -1,17 +1,105 @@ import 'package:drift/drift.dart'; import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/db/tables/player_match_table.dart'; import 'package:tallee/data/db/tables/team_table.dart'; import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/team.dart'; part 'team_dao.g.dart'; -@DriftAccessor(tables: [TeamTable]) +@DriftAccessor(tables: [TeamTable, PlayerMatchTable]) class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { TeamDao(super.db); + /* Create */ + + /// Adds a new [team] to the database. + /// Returns `true` if the team was added, `false` otherwise. + Future addTeam({required Team team, required String matchId}) async { + if (await teamExists(teamId: team.id)) return false; + await into(teamTable).insert( + TeamTableCompanion.insert( + id: team.id, + name: team.name, + createdAt: team.createdAt, + ), + mode: InsertMode.insertOrReplace, + ); + await db.batch((batch) async { + for (final player in team.members) { + await into(playerMatchTable).insert( + PlayerMatchTableCompanion.insert( + playerId: player.id, + matchId: matchId, + teamId: Value(team.id), + ), + mode: InsertMode.insertOrReplace, + ); + } + }); + return true; + } + + /// Adds multiple [teams] to the database in a batch operation. + Future addTeamsAsList({ + required List teams, + required String matchId, + }) async { + if (teams.isEmpty) return false; + + await db.batch( + (b) => b.insertAll( + teamTable, + teams + .map( + (team) => TeamTableCompanion.insert( + id: team.id, + name: team.name, + createdAt: team.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrIgnore, + ), + ); + + for (final team in teams) { + await db.batch((batch) async { + for (final player in team.members) { + await into(db.playerMatchTable).insert( + PlayerMatchTableCompanion.insert( + playerId: player.id, + matchId: matchId, + teamId: Value(team.id), + ), + mode: InsertMode.insertOrReplace, + ); + } + }); + } + return true; + } + + /* Read */ + + /// Retrieves the total count of teams in the database. + Future getTeamCount() async { + final count = + await (selectOnly(teamTable)..addColumns([teamTable.id.count()])) + .map((row) => row.read(teamTable.id.count())) + .getSingle(); + return count ?? 0; + } + + /// Checks if a team with the given [teamId] exists in the database. + /// Returns `true` if the team exists, `false` otherwise. + Future teamExists({required String teamId}) async { + final query = select(teamTable)..where((t) => t.id.equals(teamId)); + final result = await query.getSingleOrNull(); + return result != null; + } + /// Retrieves all teams from the database. - /// Note: This returns teams without their members. Use getTeamById for full team data. Future> getAllTeams() async { final query = select(teamTable); final result = await query.get(); @@ -41,8 +129,7 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { ); } - /// Helper method to get team members from player_match_table. - /// This assumes team members are tracked via the player_match_table. + /// Helper method to get team members from PlayerMatchTable. Future> _getTeamMembers({required String teamId}) async { // Get all player_match entries with this teamId final playerMatchQuery = select(db.playerMatchTable) @@ -61,44 +148,28 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { return players; } - /// Adds a new [team] to the database. - /// Returns `true` if the team was added, `false` otherwise. - Future addTeam({required Team team}) async { - if (!await teamExists(teamId: team.id)) { - await into(teamTable).insert( - TeamTableCompanion.insert( - id: team.id, - name: team.name, - createdAt: team.createdAt, - ), - mode: InsertMode.insertOrReplace, - ); - return true; - } - return false; + /* Update */ + + /// Updates the name of the team with the given [teamId]. + Future updateTeamName({ + required String teamId, + required String newName, + }) async { + final rowsAffected = + await (update(teamTable)..where((t) => t.id.equals(teamId))).write( + TeamTableCompanion(name: Value(newName)), + ); + return rowsAffected > 0; } - /// Adds multiple [teams] to the database in a batch operation. - Future addTeamsAsList({required List teams}) async { - if (teams.isEmpty) return false; + /* Delete */ - await db.batch( - (b) => b.insertAll( - teamTable, - teams - .map( - (team) => TeamTableCompanion.insert( - id: team.id, - name: team.name, - createdAt: team.createdAt, - ), - ) - .toList(), - mode: InsertMode.insertOrIgnore, - ), - ); - - return true; + /// Deletes all teams from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllTeams() async { + final query = delete(teamTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; } /// Deletes the team with the given [teamId] from the database. @@ -108,39 +179,4 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { final rowsAffected = await query.go(); return rowsAffected > 0; } - - /// Checks if a team with the given [teamId] exists in the database. - /// Returns `true` if the team exists, `false` otherwise. - Future teamExists({required String teamId}) async { - final query = select(teamTable)..where((t) => t.id.equals(teamId)); - final result = await query.getSingleOrNull(); - return result != null; - } - - /// Updates the name of the team with the given [teamId]. - Future updateTeamName({ - required String teamId, - required String newName, - }) async { - await (update(teamTable)..where((t) => t.id.equals(teamId))).write( - TeamTableCompanion(name: Value(newName)), - ); - } - - /// Retrieves the total count of teams in the database. - Future getTeamCount() async { - final count = - await (selectOnly(teamTable)..addColumns([teamTable.id.count()])) - .map((row) => row.read(teamTable.id.count())) - .getSingle(); - return count ?? 0; - } - - /// Deletes all teams from the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteAllTeams() async { - final query = delete(teamTable); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } } diff --git a/lib/data/dao/team_dao.g.dart b/lib/data/dao/team_dao.g.dart index 3b78c03..7b468dd 100644 --- a/lib/data/dao/team_dao.g.dart +++ b/lib/data/dao/team_dao.g.dart @@ -5,6 +5,12 @@ part of 'team_dao.dart'; // ignore_for_file: type=lint mixin _$TeamDaoMixin on DatabaseAccessor { $TeamTableTable get teamTable => attachedDatabase.teamTable; + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $GameTableTable get gameTable => attachedDatabase.gameTable; + $GroupTableTable get groupTable => attachedDatabase.groupTable; + $MatchTableTable get matchTable => attachedDatabase.matchTable; + $PlayerMatchTableTable get playerMatchTable => + attachedDatabase.playerMatchTable; TeamDaoManager get managers => TeamDaoManager(this); } @@ -13,4 +19,17 @@ class TeamDaoManager { TeamDaoManager(this._db); $$TeamTableTableTableManager get teamTable => $$TeamTableTableTableManager(_db.attachedDatabase, _db.teamTable); + $$PlayerTableTableTableManager get playerTable => + $$PlayerTableTableTableManager(_db.attachedDatabase, _db.playerTable); + $$GameTableTableTableManager get gameTable => + $$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable); + $$GroupTableTableTableManager get groupTable => + $$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable); + $$MatchTableTableTableManager get matchTable => + $$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable); + $$PlayerMatchTableTableTableManager get playerMatchTable => + $$PlayerMatchTableTableTableManager( + _db.attachedDatabase, + _db.playerMatchTable, + ); } diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index 2190c3d..b4f3702 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -2122,7 +2122,7 @@ class $PlayerMatchTableTable extends PlayerMatchTable type: DriftSqlType.string, requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES team_table (id)', + 'REFERENCES team_table (id) ON DELETE SET NULL', ), ); @override @@ -2820,6 +2820,13 @@ abstract class _$AppDatabase extends GeneratedDatabase { ), result: [TableUpdate('player_match_table', kind: UpdateKind.delete)], ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'team_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('player_match_table', kind: UpdateKind.update)], + ), WritePropagation( on: TableUpdateQuery.onTableName( 'player_table', diff --git a/lib/data/db/tables/player_match_table.dart b/lib/data/db/tables/player_match_table.dart index 30412ab..50dda0f 100644 --- a/lib/data/db/tables/player_match_table.dart +++ b/lib/data/db/tables/player_match_table.dart @@ -8,7 +8,9 @@ class PlayerMatchTable extends Table { text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); TextColumn get matchId => text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); - TextColumn get teamId => text().references(TeamTable, #id).nullable()(); + TextColumn get teamId => text() + .references(TeamTable, #id, onDelete: KeyAction.setNull) + .nullable()(); @override Set> get primaryKey => {playerId, matchId}; diff --git a/lib/data/models/match.dart b/lib/data/models/match.dart index 2ff02d6..b0b487c 100644 --- a/lib/data/models/match.dart +++ b/lib/data/models/match.dart @@ -4,6 +4,7 @@ import 'package:tallee/data/models/game.dart'; import 'package:tallee/data/models/group.dart'; import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/score_entry.dart'; +import 'package:tallee/data/models/team.dart'; import 'package:uuid/uuid.dart'; class Match { @@ -14,6 +15,7 @@ class Match { final Game game; final Group? group; final List players; + final List? teams; final String notes; Map scores; @@ -23,6 +25,7 @@ class Match { required this.players, this.endedAt, this.group, + this.teams, this.notes = '', String? id, DateTime? createdAt, @@ -55,6 +58,7 @@ class Match { ), group = null, players = [], + teams = [], scores = json['scores'] != null ? (json['scores'] as Map).map( (key, value) => MapEntry( @@ -78,6 +82,7 @@ class Match { 'gameId': game.id, 'groupId': group?.id, 'playerIds': players.map((player) => player.id).toList(), + 'teams': teams?.map((team) => team.toJson()).toList(), 'scores': scores.map((key, value) => MapEntry(key, value?.toJson())), 'notes': notes, }; diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index e47b220..29199f8 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -35,13 +35,11 @@ class DataTransferService { final groups = await db.groupDao.getAllGroups(); final players = await db.playerDao.getAllPlayers(); final games = await db.gameDao.getAllGames(); - final teams = await db.teamDao.getAllTeams(); final Map jsonMap = { 'players': players.map((player) => player.toJson()).toList(), 'games': games.map((game) => game.toJson()).toList(), 'groups': groups.map((group) => group.toJson()).toList(), - 'teams': teams.map((team) => team.toJson()).toList(), 'matches': matches.map((match) => match.toJson()).toList(), }; @@ -130,8 +128,6 @@ class DataTransferService { final importedGroups = parseGroupsFromJson(decodedJson, playerById); final groupById = {for (final g in importedGroups) g.id: g}; - final importedTeams = parseTeamsFromJson(decodedJson, playerById); - final importedMatches = parseMatchesFromJson( decodedJson, gameById, @@ -142,7 +138,6 @@ class DataTransferService { await db.playerDao.addPlayersAsList(players: importedPlayers); await db.gameDao.addGamesAsList(games: importedGames); await db.groupDao.addGroupsAsList(groups: importedGroups); - await db.teamDao.addTeamsAsList(teams: importedTeams); await db.matchDao.addMatchesAsList(matches: importedMatches); } @@ -190,13 +185,12 @@ class DataTransferService { }).toList(); } - /// Parses teams from JSON data. + /// Parses teams from a list of JSON objects. @visibleForTesting static List parseTeamsFromJson( - Map decodedJson, + List teamsJson, Map playerById, ) { - final teamsJson = (decodedJson['teams'] as List?) ?? []; return teamsJson.map((t) { final map = t as Map; final memberIds = (map['memberIds'] as List? ?? []) @@ -259,12 +253,16 @@ class DataTransferService { .whereType() .toList(); + final teamsJson = (map['teams'] as List?) ?? []; + final teams = parseTeamsFromJson(teamsJson, playersMap); + return Match( id: id, name: name, game: game, group: group, players: players, + teams: teams.isEmpty ? null : teams, createdAt: createdAt, endedAt: endedAt, notes: notes, diff --git a/test/db_tests/aggregates/team_test.dart b/test/db_tests/aggregates/team_test.dart index 39c5be5..592810d 100644 --- a/test/db_tests/aggregates/team_test.dart +++ b/test/db_tests/aggregates/team_test.dart @@ -1,3 +1,5 @@ +import 'dart:core' hide Match; + import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; @@ -18,8 +20,11 @@ void main() { late Team testTeam1; late Team testTeam2; late Team testTeam3; - late Game testGame1; - late Game testGame2; + late Team testTeam4; + late Game testGame; + late Match testMatch1; + late Match testMatch2; + late Match matchWithNoTeams; final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -40,27 +45,35 @@ void main() { testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]); testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]); testTeam3 = Team(name: 'Team Gamma', members: [testPlayer1, testPlayer3]); - testGame1 = Game( - name: 'Game 1', - ruleset: Ruleset.singleWinner, - description: 'Test game 1', + testTeam4 = Team(name: 'Team Omega', members: [testPlayer2, testPlayer4]); + testGame = Game( + name: 'Test Game', + ruleset: Ruleset.highestScore, color: GameColor.blue, icon: '', ); - testGame2 = Game( - name: 'Game 2', - ruleset: Ruleset.highestScore, - description: 'Test game 2', - color: GameColor.red, - icon: '', + testMatch1 = Match( + name: 'Match 1', + game: testGame, + players: [], + teams: [testTeam1, testTeam2], + ); + testMatch2 = Match( + name: 'Match 2', + game: testGame, + players: [], + teams: [testTeam3, testTeam4], + ); + matchWithNoTeams = Match( + name: 'Match with no teams', + game: testGame, + players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); }); - + await database.gameDao.addGame(game: testGame); await database.playerDao.addPlayersAsList( players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGame2); }); tearDown(() async { @@ -68,460 +81,251 @@ void main() { }); group('Team Tests', () { - // Verifies that a single team can be added and retrieved with all fields intact. - test('Adding and fetching a single team works correctly', () async { - final added = await database.teamDao.addTeam(team: testTeam1); - expect(added, true); - - final fetchedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - - expect(fetchedTeam.id, testTeam1.id); - expect(fetchedTeam.name, testTeam1.name); - expect(fetchedTeam.createdAt, testTeam1.createdAt); - }); - - // Verifies that multiple teams can be added at once and retrieved correctly. - test('Adding and fetching multiple teams works correctly', () async { - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam2, testTeam3], - ); - - final allTeams = await database.teamDao.getAllTeams(); - expect(allTeams.length, 3); - - final testTeams = { - testTeam1.id: testTeam1, - testTeam2.id: testTeam2, - testTeam3.id: testTeam3, - }; - - for (final team in allTeams) { - final testTeam = testTeams[team.id]!; - - expect(team.id, testTeam.id); - expect(team.name, testTeam.name); - expect(team.createdAt, testTeam.createdAt); - } - }); - - // Verifies that adding the same team twice does not create duplicates and returns false. - test('Adding the same team twice does not create duplicates', () async { - await database.teamDao.addTeam(team: testTeam1); - final addedAgain = await database.teamDao.addTeam(team: testTeam1); - - expect(addedAgain, false); - - final teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 1); - }); - - // Verifies that teamExists returns correct boolean based on team presence. - test('Team existence check works correctly', () async { - var teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); - expect(teamExists, false); - - await database.teamDao.addTeam(team: testTeam1); - - teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); - expect(teamExists, true); - }); - - // Verifies that deleteTeam removes the team and returns true. - test('Deleting a team works correctly', () async { - await database.teamDao.addTeam(team: testTeam1); - - final teamDeleted = await database.teamDao.deleteTeam( - teamId: testTeam1.id, - ); - expect(teamDeleted, true); - - final teamExists = await database.teamDao.teamExists( - teamId: testTeam1.id, - ); - expect(teamExists, false); - }); - - // Verifies that deleteTeam returns false for a non-existent team ID. - test('Deleting a non-existent team returns false', () async { - final teamDeleted = await database.teamDao.deleteTeam( - teamId: 'non-existent-id', - ); - expect(teamDeleted, false); - }); - - // Verifies that getTeamCount returns correct count through add/delete operations. - test('Getting the team count works correctly', () async { - var teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 0); - - await database.teamDao.addTeam(team: testTeam1); - - teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 1); - - await database.teamDao.addTeam(team: testTeam2); - - teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 2); - - await database.teamDao.deleteTeam(teamId: testTeam1.id); - - teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 1); - - await database.teamDao.deleteTeam(teamId: testTeam2.id); - - teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 0); - }); - - // Verifies that updateTeamName correctly updates only the name field. - test('Updating team name works correctly', () async { - await database.teamDao.addTeam(team: testTeam1); - - var fetchedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - expect(fetchedTeam.name, testTeam1.name); - - const newName = 'Updated Team Name'; - await database.teamDao.updateTeamName( - teamId: testTeam1.id, - newName: newName, - ); - - fetchedTeam = await database.teamDao.getTeamById(teamId: testTeam1.id); - expect(fetchedTeam.name, newName); - }); - - // Verifies that deleteAllTeams removes all teams from the database. - test('Deleting all teams works correctly', () async { - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam2, testTeam3], - ); - - var teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 3); - - final deleted = await database.teamDao.deleteAllTeams(); - expect(deleted, true); - - teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 0); - }); - - // Verifies that deleteAllTeams returns false when no teams exist. - test('Deleting all teams when empty returns false', () async { - final deleted = await database.teamDao.deleteAllTeams(); - expect(deleted, false); - }); - - // Verifies that addTeamsAsList returns false when given an empty list. - test('Adding teams as list with empty list returns false', () async { - final added = await database.teamDao.addTeamsAsList(teams: []); - expect(added, false); - }); - - // Verifies that addTeamsAsList with duplicate IDs ignores duplicates and keeps the first. - test('Adding teams with duplicate IDs ignores duplicates', () async { - final duplicateTeam = Team( - id: testTeam1.id, - name: 'Duplicate Team', - members: [testPlayer4], - ); - - await database.teamDao.addTeamsAsList( - teams: [testTeam1, duplicateTeam, testTeam2], - ); - - final teamCount = await database.teamDao.getTeamCount(); - expect(teamCount, 2); - - // The first one should be kept (insertOrIgnore) - final fetchedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - expect(fetchedTeam.name, testTeam1.name); - }); - - // Verifies that getAllTeams returns empty list when no teams exist. - test('Getting all teams when empty returns empty list', () async { - final allTeams = await database.teamDao.getAllTeams(); - expect(allTeams.isEmpty, true); - }); - - // Verifies that getTeamById throws exception for non-existent team. - test('Getting non-existent team throws exception', () async { - expect( - () => database.teamDao.getTeamById(teamId: 'non-existent-id'), - throwsA(isA()), - ); - }); - - // Verifies that updating team name preserves other fields. - test('Updating team name preserves other team fields', () async { - await database.teamDao.addTeam(team: testTeam1); - final originalTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - final originalCreatedAt = originalTeam.createdAt; - - const newName = 'Brand New Team Name'; - await database.teamDao.updateTeamName( - teamId: testTeam1.id, - newName: newName, - ); - - final updatedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - - expect(updatedTeam.name, newName); - expect(updatedTeam.id, testTeam1.id); - expect(updatedTeam.createdAt, originalCreatedAt); - }); - - // Verifies that team name can be updated to an empty string. - test('Updating team name to empty string works', () async { - await database.teamDao.addTeam(team: testTeam1); - - await database.teamDao.updateTeamName(teamId: testTeam1.id, newName: ''); - - final updatedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - - expect(updatedTeam.name, ''); - }); - - // Verifies that team name can be updated to a very long string. - test('Updating team name to long string works', () async { - await database.teamDao.addTeam(team: testTeam1); - final longName = 'A' * 500; // 500 character name - - await database.teamDao.updateTeamName( - teamId: testTeam1.id, - newName: longName, - ); - - final updatedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - - expect(updatedTeam.name, longName); - expect(updatedTeam.name.length, 500); - }); - - // Verifies that updating non-existent team name doesn't throw error. - test('Updating non-existent team name completes without error', () async { - expect( - () => database.teamDao.updateTeamName( - teamId: 'non-existent-id', - newName: 'New Name', - ), - returnsNormally, - ); - }); - - // Verifies that deleteTeam only affects the specified team. - test('Deleting one team does not affect other teams', () async { - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam2, testTeam3], - ); - - await database.teamDao.deleteTeam(teamId: testTeam2.id); - - final allTeams = await database.teamDao.getAllTeams(); - expect(allTeams.length, 2); - expect(allTeams.any((t) => t.id == testTeam1.id), true); - expect(allTeams.any((t) => t.id == testTeam2.id), false); - expect(allTeams.any((t) => t.id == testTeam3.id), true); - }); - - // Verifies that teams with overlapping members are independent. - test('Teams with overlapping members are independent', () async { - // Create two matches since player_match has primary key {playerId, matchId} - final match1 = Match( - name: 'Match 1', - game: testGame1, - players: [testPlayer1, testPlayer2], - ); - final match2 = Match( - name: 'Match 2', - game: testGame2, - players: [testPlayer1, testPlayer2], - ); - await database.matchDao.addMatch(match: match1); - await database.matchDao.addMatch(match: match2); - - // Add teams to database - await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam3]); - - // Associate players with teams through match1 - // testTeam1: player1, player2 - await database.playerMatchDao.addPlayerToMatch( - playerId: testPlayer1.id, - matchId: match1.id, - teamId: testTeam1.id, - ); - await database.playerMatchDao.addPlayerToMatch( - playerId: testPlayer2.id, - matchId: match1.id, - teamId: testTeam1.id, - ); - - // Associate players with teams through match2 - // testTeam3: player1, player3 (overlapping player1) - await database.playerMatchDao.addPlayerToMatch( - playerId: testPlayer1.id, - matchId: match2.id, - teamId: testTeam3.id, - ); - await database.playerMatchDao.addPlayerToMatch( - playerId: testPlayer3.id, - matchId: match2.id, - teamId: testTeam3.id, - ); - - final team1 = await database.teamDao.getTeamById(teamId: testTeam1.id); - final team3 = await database.teamDao.getTeamById(teamId: testTeam3.id); - - expect(team1.members.length, 2); - expect(team3.members.length, 2); - expect(team1.members.any((p) => p.id == testPlayer1.id), true); - expect(team3.members.any((p) => p.id == testPlayer1.id), true); - }); - - // Verifies that adding teams sequentially works correctly. - test('Adding teams sequentially maintains correct count', () async { - var count = await database.teamDao.getTeamCount(); - expect(count, 0); - - await database.teamDao.addTeam(team: testTeam1); - count = await database.teamDao.getTeamCount(); - expect(count, 1); - - await database.teamDao.addTeam(team: testTeam2); - count = await database.teamDao.getTeamCount(); - expect(count, 2); - - await database.teamDao.addTeam(team: testTeam3); - count = await database.teamDao.getTeamCount(); - expect(count, 3); - }); - - // Verifies that getAllTeams returns all teams with correct data. - test('Getting all teams returns all teams with correct data', () async { - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam2, testTeam3], - ); - - final allTeams = await database.teamDao.getAllTeams(); - - expect(allTeams.length, 3); - expect(allTeams.map((t) => t.id).toSet(), { - testTeam1.id, - testTeam2.id, - testTeam3.id, + group('CREATE', () { + test('Adding and fetching a single team works correctly', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); + final added = await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); + expect(added, isTrue); + + final fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + + expect(fetchedTeam.id, testTeam1.id); + expect(fetchedTeam.name, testTeam1.name); + expect(fetchedTeam.createdAt, testTeam1.createdAt); + expect(fetchedTeam.members.length, testTeam1.members.length); + for (int i = 0; i < fetchedTeam.members.length; i++) { + expect(fetchedTeam.members[i].id, testTeam1.members[i].id); + expect(fetchedTeam.members[i].name, testTeam1.members[i].name); + } + }); + + test('Adding and fetching multiple teams works correctly', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam3], + matchId: matchWithNoTeams.id, + ); + + final allTeams = await database.teamDao.getAllTeams(); + expect(allTeams.length, 3); + + final testTeams = { + testTeam1.id: testTeam1, + testTeam2.id: testTeam2, + testTeam3.id: testTeam3, + }; + + for (final team in allTeams) { + final testTeam = testTeams[team.id]!; + + expect(team.id, testTeam.id); + expect(team.name, testTeam.name); + expect(team.createdAt, testTeam.createdAt); + } + }); + + test('addTeam() ignores duplicates', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); + var added = await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); + expect(added, isTrue); + + added = await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); + expect(added, isFalse); + + final teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 1); + }); + + test('addTeamsAsList() with empty list returns isFalse', () async { + final added = await database.teamDao.addTeamsAsList( + teams: [], + matchId: matchWithNoTeams.id, + ); + expect(added, isFalse); + }); + + test('addTeamsAsList() ignores duplicates', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); + final added = await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam1], + matchId: matchWithNoTeams.id, + ); + expect(added, isTrue); + + final teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 2); }); }); - // Verifies that teamExists returns false for deleted teams. - test('Team existence returns false after deletion', () async { - await database.teamDao.addTeam(team: testTeam1); - expect(await database.teamDao.teamExists(teamId: testTeam1.id), true); + group('READ', () { + test('getTeamCount works correctly', () async { + var count = await database.teamDao.getTeamCount(); + expect(count, 0); - await database.teamDao.deleteTeam(teamId: testTeam1.id); - expect(await database.teamDao.teamExists(teamId: testTeam1.id), false); + await database.matchDao.addMatch(match: testMatch1); + + count = await database.teamDao.getTeamCount(); + expect(count, 2); + + await database.teamDao.addTeam( + team: testTeam2, + matchId: matchWithNoTeams.id, + ); + + count = await database.teamDao.getTeamCount(); + expect(count, 2); + + await database.teamDao.deleteTeam(teamId: testTeam1.id); + + count = await database.teamDao.getTeamCount(); + expect(count, 1); + + await database.teamDao.deleteTeam(teamId: testTeam2.id); + + count = await database.teamDao.getTeamCount(); + expect(count, 0); + }); + + test('teamExists() works correctly', () async { + var teamExists = await database.teamDao.teamExists( + teamId: testTeam1.id, + ); + expect(teamExists, isFalse); + + await database.matchDao.addMatch(match: matchWithNoTeams); + + await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); + + teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); + expect(teamExists, isTrue); + }); + + test('getAllTeams() with no teams returns empty list', () async { + final allTeams = await database.teamDao.getAllTeams(); + expect(allTeams, isA>()); + expect(allTeams.isEmpty, isTrue); + }); + + test('getAllTeams() works correctly', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); + + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam3], + matchId: matchWithNoTeams.id, + ); + + final allTeams = await database.teamDao.getAllTeams(); + + expect(allTeams.length, 3); + expect(allTeams.map((t) => t.id).toSet(), { + testTeam1.id, + testTeam2.id, + testTeam3.id, + }); + }); + + test('Getting non-existent team throws exception', () async { + expect( + () => database.teamDao.getTeamById(teamId: 'non-existent-id'), + throwsA(isA()), + ); + }); }); - // Verifies that adding multiple teams in batch then deleting returns correct count. - test('Batch add then partial delete maintains correct count', () async { - await database.teamDao.addTeamsAsList( - teams: [testTeam1, testTeam2, testTeam3], - ); + group('UPDATED', () { + test('updateTeamName() works correctly', () async { + await database.matchDao.addMatch(match: matchWithNoTeams); - expect(await database.teamDao.getTeamCount(), 3); + await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); - await database.teamDao.deleteTeam(teamId: testTeam1.id); - expect(await database.teamDao.getTeamCount(), 2); + var fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + expect(fetchedTeam.name, testTeam1.name); - await database.teamDao.deleteTeam(teamId: testTeam3.id); - expect(await database.teamDao.getTeamCount(), 1); + const newName = 'New name'; + await database.teamDao.updateTeamName( + teamId: testTeam1.id, + newName: newName, + ); + + fetchedTeam = await database.teamDao.getTeamById(teamId: testTeam1.id); + expect(fetchedTeam.name, newName); + }); + + test('updateTeamName() does nothing for non-existent team', () async { + final updated = await database.teamDao.updateTeamName( + teamId: 'non-existing-id', + newName: 'New Name', + ); + expect(updated, isFalse); + + final allTeams = await database.teamDao.getAllTeams(); + expect(allTeams, isEmpty); + }); }); - // Verifies that deleteAllTeams with single team works. - test('Deleting all teams with single team returns true', () async { - await database.teamDao.addTeam(team: testTeam1); - expect(await database.teamDao.getTeamCount(), 1); + group('DELETE', () { + test('deleteTeam() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.matchDao.addMatch(match: matchWithNoTeams); - final deleted = await database.teamDao.deleteAllTeams(); - expect(deleted, true); - expect(await database.teamDao.getTeamCount(), 0); - }); + await database.teamDao.addTeam( + team: testTeam1, + matchId: matchWithNoTeams.id, + ); - // Verifies that addTeam after deleteAllTeams works correctly. - test('Adding team after deleteAllTeams works correctly', () async { - await database.teamDao.addTeamsAsList(teams: [testTeam1, testTeam2]); - expect(await database.teamDao.getTeamCount(), 2); + final deleted = await database.teamDao.deleteTeam(teamId: testTeam1.id); + expect(deleted, isTrue); - await database.teamDao.deleteAllTeams(); - expect(await database.teamDao.getTeamCount(), 0); + final teamExists = await database.teamDao.teamExists( + teamId: testTeam1.id, + ); + expect(teamExists, isFalse); + }); - final added = await database.teamDao.addTeam(team: testTeam3); - expect(added, true); - expect(await database.teamDao.getTeamCount(), 1); + test('Deleting a non-existent team returns isFalse', () async { + final deleted = await database.teamDao.deleteTeam( + teamId: 'non-existent-id', + ); + expect(deleted, isFalse); + }); - final fetchedTeam = await database.teamDao.getTeamById( - teamId: testTeam3.id, - ); - expect(fetchedTeam.name, testTeam3.name); - }); + test('deleteAllTeams() works correctly', () async { + await database.matchDao.addMatchesAsList( + matches: [testMatch1, testMatch2], + ); + var teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 4); - // Verifies that addTeamsAsList with partial duplicates ignores duplicates. - test('Adding teams with some duplicates ignores only duplicates', () async { - await database.teamDao.addTeam(team: testTeam1); + final deleted = await database.teamDao.deleteAllTeams(); + expect(deleted, isTrue); - final duplicateTeam1 = Team( - id: testTeam1.id, - name: 'Different Name', - members: [testPlayer3], - ); + teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 0); + }); - await database.teamDao.addTeamsAsList( - teams: [duplicateTeam1, testTeam2, testTeam3], - ); - - final allTeams = await database.teamDao.getAllTeams(); - expect(allTeams.length, 3); - - // Verify testTeam1 retained original name (was inserted first) - final team1 = await database.teamDao.getTeamById(teamId: testTeam1.id); - expect(team1.name, testTeam1.name); - }); - - // Verifies that team IDs are preserved correctly. - test('Team IDs are preserved through add and retrieve', () async { - await database.teamDao.addTeam(team: testTeam1); - - final fetchedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - - expect(fetchedTeam.id, testTeam1.id); - }); - - // Verifies that createdAt timestamps are preserved. - test('Team createdAt timestamps are preserved', () async { - await database.teamDao.addTeam(team: testTeam1); - - final fetchedTeam = await database.teamDao.getTeamById( - teamId: testTeam1.id, - ); - - expect(fetchedTeam.createdAt, testTeam1.createdAt); + test('deleteAllTeams() with empty list returns false', () async { + final deleted = await database.teamDao.deleteAllTeams(); + expect(deleted, isFalse); + }); }); }); } diff --git a/test/services/data_transfer_service_test.dart b/test/services/data_transfer_service_test.dart index e863629..6aec390 100644 --- a/test/services/data_transfer_service_test.dart +++ b/test/services/data_transfer_service_test.dart @@ -99,7 +99,9 @@ void main() { await database.playerDao.addPlayer(player: testPlayer1); await database.gameDao.addGame(game: testGame); await database.groupDao.addGroup(group: testGroup); + /* await database.teamDao.addTeam(team: testTeam); +*/ await database.matchDao.addMatch(match: testMatch); var playerCount = await database.playerDao.getPlayerCount(); @@ -137,7 +139,9 @@ void main() { await database.playerDao.addPlayer(player: testPlayer2); await database.gameDao.addGame(game: testGame); await database.groupDao.addGroup(group: testGroup); + /* await database.teamDao.addTeam(team: testTeam); +*/ await database.matchDao.addMatch(match: testMatch); final ctx = await getContext(tester); @@ -244,7 +248,9 @@ void main() { }); testWidgets('Team data is correct', (tester) async { + /* await database.teamDao.addTeam(team: testTeam); +*/ final ctx = await getContext(tester); final jsonString = await DataTransferService.getAppDataAsJson(ctx); @@ -644,19 +650,17 @@ void main() { test('parseTeamsFromJson()', () { final playerById = {testPlayer1.id: testPlayer1}; - final jsonMap = { - 'teams': [ - { - 'id': testTeam.id, - 'name': testTeam.name, - 'memberIds': [testPlayer1.id], - 'createdAt': testTeam.createdAt.toIso8601String(), - }, - ], - }; + final teamsJson = [ + { + 'id': testTeam.id, + 'name': testTeam.name, + 'memberIds': [testPlayer1.id], + 'createdAt': testTeam.createdAt.toIso8601String(), + }, + ]; final teams = DataTransferService.parseTeamsFromJson( - jsonMap, + teamsJson, playerById, ); @@ -668,15 +672,21 @@ void main() { }); test('parseTeamsFromJson() empty list', () { - final jsonMap = {'teams': []}; - final teams = DataTransferService.parseTeamsFromJson(jsonMap, {}); + final teams = DataTransferService.parseTeamsFromJson([], {}); expect(teams, isEmpty); }); - test('parseTeamsFromJson() missing key', () { - final jsonMap = {}; - final teams = DataTransferService.parseTeamsFromJson(jsonMap, {}); - expect(teams, isEmpty); + test('parseTeamsFromJson() missing memberIds', () { + final teamsJson = [ + { + 'id': testTeam.id, + 'name': testTeam.name, + 'createdAt': testTeam.createdAt.toIso8601String(), + }, + ]; + final teams = DataTransferService.parseTeamsFromJson(teamsJson, {}); + expect(teams.length, 1); + expect(teams[0].members, isEmpty); }); test('parseMatchesFromJson()', () { -- 2.49.1 From cbc8a1afcec9025b7b8c86886f4b578d13f86137 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 1 May 2026 16:49:33 +0200 Subject: [PATCH 09/15] Updated player-match dao and tests --- lib/data/dao/match_dao.dart | 47 +- lib/data/dao/player_match_dao.dart | 134 +-- .../relationships/player_match_test.dart | 928 +++++++----------- 3 files changed, 415 insertions(+), 694 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index faa0227..11cd5a2 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -270,8 +270,9 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { if (row.groupId != null) { group = await db.groupDao.getGroupById(groupId: row.groupId!); } - final players = - await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; + final players = await db.playerMatchDao.getPlayersOfMatch( + matchId: row.id, + ); final scores = await db.scoreEntryDao.getAllMatchScores( matchId: row.id, @@ -307,8 +308,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { group = await db.groupDao.getGroupById(groupId: result.groupId!); } - final players = - await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? []; + final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId); final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId); @@ -338,8 +338,9 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { rows.map((row) async { final game = await db.gameDao.getGameById(gameId: row.gameId); final group = await db.groupDao.getGroupById(groupId: groupId); - final players = - await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? []; + final players = await db.playerMatchDao.getPlayersOfMatch( + matchId: row.id, + ); final teams = await _getMatchTeams(matchId: row.id); return Match( id: row.id, @@ -447,40 +448,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { return rowsAffected > 0; } - /// Replaces all players in a match with the provided list of players. - /// Removes all existing players from the match and adds the new players. - /// Also adds any new players to the player table if they don't exist. - Future updateMatchPlayers({ - required String matchId, - required List newPlayers, - }) async { - await db.transaction(() async { - // Remove all existing players from the match - final deleteQuery = delete(db.playerMatchTable) - ..where((p) => p.matchId.equals(matchId)); - await deleteQuery.go(); - - // Add new players to the player table if they don't exist - await Future.wait( - newPlayers.map((player) async { - if (!await db.playerDao.playerExists(playerId: player.id)) { - await db.playerDao.addPlayer(player: player); - } - }), - ); - - // Add the new players to the match - await Future.wait( - newPlayers.map( - (player) => db.playerMatchDao.addPlayerToMatch( - matchId: matchId, - playerId: player.id, - ), - ), - ); - }); - } - /* Delete */ /// Deletes the match with the given [matchId] from the database. diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index b467a1b..1c9d0dd 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -11,14 +11,16 @@ class PlayerMatchDao extends DatabaseAccessor with _$PlayerMatchDaoMixin { PlayerMatchDao(super.db); + /* Create */ + /// Associates a player with a match by inserting a record into the /// [PlayerMatchTable]. Optionally associates with a team and sets initial score. - Future addPlayerToMatch({ + Future addPlayerToMatch({ required String matchId, required String playerId, String? teamId, }) async { - await into(playerMatchTable).insert( + final rowsAffected = await into(playerMatchTable).insert( PlayerMatchTableCompanion.insert( playerId: playerId, matchId: matchId, @@ -26,42 +28,14 @@ class PlayerMatchDao extends DatabaseAccessor ), mode: InsertMode.insertOrReplace, ); - } - - /// Retrieves a list of [Player]s associated with the given [matchId]. - /// Returns null if no players are found. - Future?> getPlayersOfMatch({required String matchId}) async { - final result = await (select( - playerMatchTable, - )..where((p) => p.matchId.equals(matchId))).get(); - - if (result.isEmpty) return null; - - final futures = result.map( - (row) => db.playerDao.getPlayerById(playerId: row.playerId), - ); - final players = await Future.wait(futures); - return players; - } - - /// Updates the team for a player in a match. - /// Returns `true` if the update was successful, otherwise `false`. - Future updatePlayerTeam({ - required String matchId, - 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))); return rowsAffected > 0; } + /* Read */ + /// Checks if there are any players associated with the given [matchId]. /// Returns `true` if there are players, otherwise `false`. - Future matchHasPlayers({required String matchId}) async { + Future hasMatchPlayers({required String matchId}) async { final count = await (selectOnly(playerMatchTable) ..where(playerMatchTable.matchId.equals(matchId)) @@ -87,32 +61,80 @@ class PlayerMatchDao extends DatabaseAccessor return (count ?? 0) > 0; } - /// Removes the association of a player with a match by deleting the record - /// from the [PlayerMatchTable]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removePlayerFromMatch({ + /// Retrieves a list of [Player]s associated with the given [matchId]. + /// Returns empty list if no players are found. + Future> getPlayersOfMatch({required String matchId}) async { + final result = await (select( + playerMatchTable, + )..where((p) => p.matchId.equals(matchId))).get(); + + if (result.isEmpty) return []; + + final futures = result.map( + (row) => db.playerDao.getPlayerById(playerId: row.playerId), + ); + final players = await Future.wait(futures); + return players; + } + + /// Retrieves a list of [Player]s associated with a specific team in a match. + /// Returns empty list if no players are found for the team in the match. + Future> getPlayersOfTeamInMatch({ + required String matchId, + required String teamId, + }) async { + final result = + await (select(playerMatchTable) + ..where((p) => p.matchId.equals(matchId)) + ..where((p) => p.teamId.equals(teamId))) + .get(); + + if (result.isEmpty) return []; + + final futures = result.map( + (row) => db.playerDao.getPlayerById(playerId: row.playerId), + ); + final players = await Future.wait(futures); + return players; + } + + /* Updated */ + + /// Updates the team for a player in a match. + /// Returns `true` if the update was successful, otherwise `false`. + Future updatePlayersTeam({ required String matchId, required String playerId, + required String? teamId, }) async { - final query = delete(playerMatchTable) - ..where((pg) => pg.matchId.equals(matchId)) - ..where((pg) => pg.playerId.equals(playerId)); - final rowsAffected = await query.go(); + final rowsAffected = + await (update(playerMatchTable)..where( + (p) => p.matchId.equals(matchId) & p.playerId.equals(playerId), + )) + .write(PlayerMatchTableCompanion(teamId: Value(teamId))); return rowsAffected > 0; } /// Updates the players associated with a match based on the provided /// [newPlayer] list. It adds new players and removes players that are no /// longer associated with the match. - Future updatePlayersFromMatch({ + Future updateMatchPlayers({ required String matchId, required List newPlayer, }) async { + if (newPlayer.isEmpty) return false; + final currentPlayers = await getPlayersOfMatch(matchId: matchId); // Create sets of player IDs for easy comparison - final currentPlayerIds = currentPlayers?.map((p) => p.id).toSet() ?? {}; + final currentPlayerIds = currentPlayers.map((p) => p.id).toSet(); final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet(); + // Are the current and new player identical? + if (currentPlayerIds.containsAll(newPlayerIdsSet) && + newPlayerIdsSet.containsAll(currentPlayerIds)) { + return false; + } + // Determine players to add and remove final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds); final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet); @@ -147,22 +169,22 @@ class PlayerMatchDao extends DatabaseAccessor ); } }); + return true; } - /// Retrieves all players in a specific team for a match. - Future> getPlayersInTeam({ + /* Delete */ + + /// Removes the association of a player with a match by deleting the record + /// from the [PlayerMatchTable]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removePlayerFromMatch({ required String matchId, - required String teamId, + required String playerId, }) async { - 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), - ); - return Future.wait(futures); + final query = delete(playerMatchTable) + ..where((pg) => pg.matchId.equals(matchId)) + ..where((pg) => pg.playerId.equals(playerId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; } } diff --git a/test/db_tests/relationships/player_match_test.dart b/test/db_tests/relationships/player_match_test.dart index 92601f0..2cc5185 100644 --- a/test/db_tests/relationships/player_match_test.dart +++ b/test/db_tests/relationships/player_match_test.dart @@ -5,7 +5,6 @@ import 'package:flutter_test/flutter_test.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/team.dart'; @@ -17,11 +16,8 @@ void main() { late Player testPlayer3; late Player testPlayer4; late Player testPlayer5; - late Player testPlayer6; - late Group testGroup; late Game testGame; - late Match testMatchOnlyGroup; - late Match testMatchOnlyPlayers; + late Match testMatch1; late Team testTeam1; late Team testTeam2; final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); @@ -42,12 +38,6 @@ void main() { testPlayer3 = Player(name: 'Charlie'); testPlayer4 = Player(name: 'Diana'); testPlayer5 = Player(name: 'Eve'); - testPlayer6 = Player(name: 'Frank'); - testGroup = Group( - name: 'Test Group', - description: '', - members: [testPlayer1, testPlayer2, testPlayer3], - ); testGame = Game( name: 'Test Game', ruleset: Ruleset.singleWinner, @@ -55,31 +45,17 @@ void main() { color: GameColor.blue, icon: '', ); - testMatchOnlyGroup = Match( - name: 'Test Match with Group', - game: testGame, - players: testGroup.members, - group: testGroup, - ); - testMatchOnlyPlayers = Match( + testMatch1 = Match( name: 'Test Match with Players', game: testGame, - players: [testPlayer4, testPlayer5, testPlayer6], + players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]); testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]); }); await database.playerDao.addPlayersAsList( - players: [ - testPlayer1, - testPlayer2, - testPlayer3, - testPlayer4, - testPlayer5, - testPlayer6, - ], + players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], ); - await database.groupDao.addGroup(group: testGroup); await database.gameDao.addGame(game: testGame); }); tearDown(() async { @@ -87,603 +63,359 @@ void main() { }); group('Player-Match Tests', () { - test('Match has player works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - await database.playerDao.addPlayer(player: testPlayer1); + group('CREATE', () { + test('addPlayerToMatch() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.playerDao.addPlayer(player: testPlayer1); - var matchHasPlayers = await database.playerMatchDao.matchHasPlayers( - matchId: testMatchOnlyGroup.id, - ); - - expect(matchHasPlayers, true); - - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - ); - - matchHasPlayers = await database.playerMatchDao.matchHasPlayers( - matchId: testMatchOnlyGroup.id, - ); - - expect(matchHasPlayers, true); - }); - - test('Adding a player to a match works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - await database.playerDao.addPlayer(player: testPlayer5); - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer5.id, - ); - - var playerAdded = await database.playerMatchDao.isPlayerInMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer5.id, - ); - - expect(playerAdded, true); - - playerAdded = await database.playerMatchDao.isPlayerInMatch( - matchId: testMatchOnlyGroup.id, - playerId: '', - ); - - expect(playerAdded, false); - }); - - test('Removing player from match works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - final playerToRemove = testMatchOnlyPlayers.players[0]; - - final removed = await database.playerMatchDao.removePlayerFromMatch( - playerId: playerToRemove.id, - matchId: testMatchOnlyPlayers.id, - ); - expect(removed, true); - - final result = await database.matchDao.getMatchById( - matchId: testMatchOnlyPlayers.id, - ); - expect(result.players.length, testMatchOnlyPlayers.players.length - 1); - - final playerExists = result.players.any((p) => p.id == playerToRemove.id); - expect(playerExists, false); - }); - - test('Retrieving players of a match works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - 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); - } - }); - - test('Updating the match players works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - final newPlayers = [testPlayer1, testPlayer2, testPlayer4]; - await database.playerDao.addPlayersAsList(players: newPlayers); - - // First, remove all existing players - final existingPlayers = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, - ); - - if (existingPlayers == null || existingPlayers.isEmpty) { - fail('Existing players should not be null or empty'); - } - - await database.playerMatchDao.updatePlayersFromMatch( - matchId: testMatchOnlyPlayers.id, - newPlayer: newPlayers, - ); - - final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, - ); - - if (updatedPlayers == null) { - fail('Updated players should not be null'); - } - - expect(updatedPlayers.length, newPlayers.length); - - /// Create a map of new players for easy lookup - final testPlayers = {for (var p in newPlayers) p.id: p}; - - /// Verify each updated player matches the new players - for (final player in updatedPlayers) { - final testPlayer = testPlayers[player.id]!; - - expect(player.id, testPlayer.id); - expect(player.name, testPlayer.name); - expect(player.createdAt, testPlayer.createdAt); - } - }); - - test( - 'Adding the same player to separate matches works correctly', - () async { - final playersList = [testPlayer1, testPlayer2, testPlayer3]; - final match1 = Match( - name: 'Match 1', - game: testGame, - players: playersList, - notes: '', + var added = await database.playerMatchDao.addPlayerToMatch( + matchId: testMatch1.id, + playerId: testPlayer1.id, ); - final match2 = Match( - name: 'Match 2', - game: testGame, - players: playersList, - notes: '', + expect(added, isTrue); + + added = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatch1.id, + playerId: testPlayer1.id, ); + expect(added, isTrue); + }); - await Future.wait([ - database.matchDao.addMatch(match: match1), - database.matchDao.addMatch(match: match2), - ]); + test('addPlayerToMatch() with team works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.teamDao.addTeam(team: testTeam1, matchId: testMatch1.id); - final players1 = await database.playerMatchDao.getPlayersOfMatch( - matchId: match1.id, - ); - final players2 = await database.playerMatchDao.getPlayersOfMatch( - matchId: match2.id, - ); - - expect(players1, isNotNull); - expect(players2, isNotNull); - - expect( - players1!.map((p) => p.id).toList(), - equals(players2!.map((p) => p.id).toList()), - ); - expect( - players1.map((p) => p.name).toList(), - equals(players2.map((p) => p.name).toList()), - ); - expect( - players1.map((p) => p.createdAt).toList(), - equals(players2.map((p) => p.createdAt).toList()), - ); - }, - ); - - // Verifies that getPlayersOfMatch returns null for a non-existent match. - test('getPlayersOfMatch returns null for non-existent match', () async { - final players = await database.playerMatchDao.getPlayersOfMatch( - matchId: 'non-existent-match-id', - ); - - expect(players, isNull); - }); - - test('Adding player with 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, - ); - - final playersInTeam = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam1.id, - ); - - expect(playersInTeam.length, 1); - expect(playersInTeam[0].id, testPlayer1.id); - }); - - test('updatePlayerTeam updates team correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - await database.teamDao.addTeam(team: testTeam1); - await database.teamDao.addTeam(team: testTeam2); - - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - teamId: testTeam1.id, - ); - - // Update player's team - final updated = await database.playerMatchDao.updatePlayerTeam( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - teamId: testTeam2.id, - ); - - expect(updated, true); - - // Verify player is now in testTeam2 - final playersInTeam2 = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam2.id, - ); - - expect(playersInTeam2.length, 1); - expect(playersInTeam2[0].id, testPlayer1.id); - - // Verify player is no longer in testTeam1 - final playersInTeam1 = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam1.id, - ); - - expect(playersInTeam1.isEmpty, true); - }); - - test('updatePlayerTeam can remove player from team', () 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, - ); - - // Remove player from team by setting teamId to null - final updated = await database.playerMatchDao.updatePlayerTeam( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - teamId: null, - ); - - expect(updated, true); - - final playersInTeam = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam1.id, - ); - - expect(playersInTeam.isEmpty, true); - }); - - 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', + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatch1.id, + playerId: testPlayer3.id, teamId: testTeam1.id, ); - expect(updated, false); - }, - ); + final playersInTeam = await database.playerMatchDao + .getPlayersOfTeamInMatch( + matchId: testMatch1.id, + teamId: testTeam1.id, + ); - // Verifies that getPlayersInTeam returns empty list for non-existent team. - test('getPlayersInTeam returns empty list for non-existent team', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); + expect(playersInTeam, isNotEmpty); + expect(playersInTeam.length, 3); + }); - final players = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyPlayers.id, - teamId: 'non-existent-team-id', - ); + test('addPlayerToMatch() ignores duplicates', () async { + await database.matchDao.addMatch(match: testMatch1); - expect(players.isEmpty, true); - }); - - test('getPlayersInTeam returns all players of a team', () 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, - ); - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer2.id, - teamId: testTeam1.id, - ); - - final playersInTeam = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam1.id, - ); - expect(playersInTeam.length, 2); - final playerIds = playersInTeam.map((p) => p.id).toSet(); - expect(playerIds.contains(testPlayer1.id), true); - expect(playerIds.contains(testPlayer2.id), true); - }); - - 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 isInMatch = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatch1.id, + playerId: testPlayer5.id, ); + expect(isInMatch, isFalse); - expect(removed, false); - }, - ); - - 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, - ); - - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - ); - - final players = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyGroup.id, - ); - - expect(players?.length, 3); - }); - - 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, + matchId: testMatch1.id, ); - expect(players?.length, 3); + expect(players.length, testMatch1.players.length); - // Update with empty list - await database.playerMatchDao.updatePlayersFromMatch( - matchId: testMatchOnlyPlayers.id, + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatch1.id, + playerId: testPlayer1.id, + ); + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatch1.id, + playerId: testPlayer1.id, + ); + + players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatch1.id, + ); + + expect(players.length, testMatch1.players.length + 1); + }); + }); + + group('READ', () { + test('hasMatchPlayers() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + var matchHasPlayers = await database.playerMatchDao.hasMatchPlayers( + matchId: testMatch1.id, + ); + expect(matchHasPlayers, isTrue); + }); + + test('hasMatchPlayers() returns false for non-existent match', () async { + final hasPlayers = await database.playerMatchDao.hasMatchPlayers( + matchId: 'non-existent-match-id', + ); + expect(hasPlayers, isFalse); + }); + + test('isPlayerInMatch() works correctly', () async { + final isInMatch = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatch1.id, + playerId: testPlayer1.id, + ); + expect(isInMatch, isTrue); + }); + + test('isPlayerInMatch() returns false for non-existent match', () async { + final isInMatch = await database.playerMatchDao.isPlayerInMatch( + matchId: 'non-existent-match-id', + playerId: testPlayer1.id, + ); + expect(isInMatch, isFalse); + }); + + test('getPlayersOfMatch() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatch1.id, + ); + expect(players, isNotEmpty); + + for (int i = 0; i < players.length; i++) { + expect(players[i].id, testMatch1.players[i].id); + expect(players[i].name, testMatch1.players[i].name); + expect(players[i].createdAt, testMatch1.players[i].createdAt); + } + }); + + test( + 'getPlayersOfMatch() returns empty list for non-existent match', + () async { + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: 'non-existent-match-id', + ); + expect(players, isEmpty); + }, + ); + + test('getPlayersInTeam() works correctly', () async { + // Create a match with teams + final matchWithTeams = Match( + name: 'Match with teams', + game: testGame, + players: [], + teams: [testTeam1, testTeam2], + ); + await database.matchDao.addMatch(match: matchWithTeams); + + var playersInTeam = await database.playerMatchDao + .getPlayersOfTeamInMatch( + matchId: matchWithTeams.id, + teamId: testTeam1.id, + ); + + expect(playersInTeam, isNotEmpty); + expect(playersInTeam.length, 2); + + var playerIds = playersInTeam.map((p) => p.id).toSet(); + expect(playerIds.contains(testPlayer1.id), isTrue); + expect(playerIds.contains(testPlayer2.id), isTrue); + + playersInTeam = await database.playerMatchDao.getPlayersOfTeamInMatch( + matchId: matchWithTeams.id, + teamId: testTeam2.id, + ); + + expect(playersInTeam, isNotEmpty); + expect(playersInTeam.length, 2); + + playerIds = playersInTeam.map((p) => p.id).toSet(); + expect(playerIds.contains(testPlayer3.id), isTrue); + expect(playerIds.contains(testPlayer4.id), isTrue); + }); + + test( + 'getPlayersInTeam() returns empty list for non-existent match', + () async { + final players = await database.playerMatchDao.getPlayersOfTeamInMatch( + matchId: 'non-existent-match-id', + teamId: testTeam1.id, + ); + expect(players, isEmpty); + }, + ); + }); + + group('UPDATE', () { + test('updateMatchPlayers() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + + final newPlayers = [testPlayer1, testPlayer2, testPlayer4]; + await database.playerDao.addPlayersAsList(players: newPlayers); + + final existingPlayers = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatch1.id, + ); + expect(existingPlayers, isNotEmpty); + + await database.playerMatchDao.updateMatchPlayers( + matchId: testMatch1.id, + newPlayer: newPlayers, + ); + + final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatch1.id, + ); + expect(updatedPlayers, isNotEmpty); + expect(updatedPlayers.length, newPlayers.length); + + /// Create a map of new players for easy lookup + final testPlayers = {for (var p in newPlayers) p.id: p}; + + for (final player in updatedPlayers) { + final testPlayer = testPlayers[player.id]!; + + expect(player.id, testPlayer.id); + expect(player.name, testPlayer.name); + expect(player.createdAt, testPlayer.createdAt); + } + }); + + test('updatePlayersTeam() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.teamDao.addTeam(team: testTeam1, matchId: testMatch1.id); + await database.teamDao.addTeam(team: testTeam2, matchId: testMatch1.id); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatch1.id, + playerId: testPlayer1.id, + teamId: testTeam1.id, + ); + + final updated = await database.playerMatchDao.updatePlayersTeam( + matchId: testMatch1.id, + playerId: testPlayer1.id, + teamId: testTeam2.id, + ); + + expect(updated, isTrue); + + final playersInTeam2 = await database.playerMatchDao + .getPlayersOfTeamInMatch( + matchId: testMatch1.id, + teamId: testTeam2.id, + ); + expect(playersInTeam2, isNotEmpty); + expect(playersInTeam2.length, 3); + + final playersInTeam1 = await database.playerMatchDao + .getPlayersOfTeamInMatch( + matchId: testMatch1.id, + teamId: testTeam1.id, + ); + + expect(playersInTeam1, isNotEmpty); + expect(playersInTeam1.length, 1); + expect(playersInTeam1[0].id, testPlayer2.id); + }); + + test( + 'updatePlayersTeam() returns false for non-existent player-match', + () async { + await database.matchDao.addMatch(match: testMatch1); + + final updated = await database.playerMatchDao.updatePlayersTeam( + matchId: testMatch1.id, + playerId: 'non-existent-player-id', + teamId: testTeam1.id, + ); + expect(updated, isFalse); + }, + ); + + test('updateMatchPlayers() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + + var matchPlayers = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); + expect(matchPlayers.players.length, testMatch1.players.length); + + final newPlayersList = [testPlayer1, testPlayer2]; + await database.playerMatchDao.updateMatchPlayers( + matchId: testMatch1.id, + newPlayer: newPlayersList, + ); + + matchPlayers = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); + + expect(matchPlayers.players.length, 2); + expect(matchPlayers.players.any((p) => p.id == testPlayer1.id), isTrue); + expect(matchPlayers.players.any((p) => p.id == testPlayer2.id), isTrue); + }); + + test('updateMatchPlayers() with same players makes is ignored', () async { + await database.matchDao.addMatch(match: testMatch1); + + final originalPlayers = testMatch1.players; + + final updated = await database.playerMatchDao.updateMatchPlayers( + matchId: testMatch1.id, + newPlayer: originalPlayers, + ); + expect(updated, isFalse); + + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatch1.id, + ); + + expect(players.length, originalPlayers.length); + final playerIds = players.map((p) => p.id).toSet(); + for (final originalPlayer in originalPlayers) { + expect(playerIds.contains(originalPlayer.id), isTrue); + } + }); + + test('updateMatchPlayers() with empty list returns false', () async { + await database.matchDao.addMatch(match: testMatch1); + final updated = await database.playerMatchDao.updateMatchPlayers( + matchId: testMatch1.id, newPlayer: [], ); - // Verify all players are removed - players = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, + expect(updated, isFalse); + }); + }); + + group('DELETE', () { + test('removePlayerFromMatch() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + + final playerToRemove = testMatch1.players[0]; + + final removed = await database.playerMatchDao.removePlayerFromMatch( + playerId: playerToRemove.id, + matchId: testMatch1.id, ); - expect(players, isNull); - }, - ); + expect(removed, isTrue); - test('updatePlayersFromMatch with same players makes no changes', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - final originalPlayers = [testPlayer4, testPlayer5, testPlayer6]; - - await database.playerMatchDao.updatePlayersFromMatch( - matchId: testMatchOnlyPlayers.id, - newPlayer: originalPlayers, - ); - - final players = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, - ); - - expect(players?.length, originalPlayers.length); - final playerIds = players!.map((p) => p.id).toSet(); - for (final originalPlayer in originalPlayers) { - expect(playerIds.contains(originalPlayer.id), true); - } - }); - - test('matchHasPlayers returns false for non-existent match', () async { - final hasPlayers = await database.playerMatchDao.matchHasPlayers( - matchId: 'non-existent-match-id', - ); - - expect(hasPlayers, false); - }); - - test('isPlayerInMatch returns false for non-existent match', () async { - final isInMatch = await database.playerMatchDao.isPlayerInMatch( - matchId: 'non-existent-match-id', - playerId: testPlayer1.id, - ); - - expect(isInMatch, false); - }); - - // 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); - - final players = await database.playerMatchDao.getPlayersInTeam( - matchId: 'non-existent-match-id', - teamId: testTeam1.id, + final result = await database.matchDao.getMatchById( + matchId: testMatch1.id, ); + expect(result.players.length, testMatch1.players.length - 1); - 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 { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - await database.teamDao.addTeam(team: testTeam1); - await database.teamDao.addTeam(team: testTeam2); - - // Add players to different teams - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - teamId: testTeam1.id, - ); - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer2.id, - teamId: testTeam1.id, - ); - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer3.id, - teamId: testTeam2.id, - ); - - // Verify team 1 players - final playersInTeam1 = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam1.id, - ); - - expect(playersInTeam1.length, 2); - final team1Ids = playersInTeam1.map((p) => p.id).toSet(); - expect(team1Ids.contains(testPlayer1.id), true); - expect(team1Ids.contains(testPlayer2.id), true); - expect(team1Ids.contains(testPlayer3.id), false); - - // Verify team 2 players - final playersInTeam2 = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam2.id, - ); - expect(playersInTeam2.length, 1); - expect(playersInTeam2[0].id, testPlayer3.id); - }); - - // 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, - ); - final match2 = Match( - name: 'Match 2', - game: testGame, - players: playersList, - ); - - await Future.wait([ - database.matchDao.addMatch(match: match1), - database.matchDao.addMatch(match: match2), - ]); - - // Remove player from match1 - final removed = await database.playerMatchDao.removePlayerFromMatch( - playerId: testPlayer1.id, - matchId: match1.id, - ); - expect(removed, true); - - // Verify player is removed from match1 - final isInMatch1 = await database.playerMatchDao.isPlayerInMatch( - matchId: match1.id, - playerId: testPlayer1.id, - ); - expect(isInMatch1, false); - - // Verify player still exists in match2 - final isInMatch2 = await database.playerMatchDao.isPlayerInMatch( - matchId: match2.id, - playerId: testPlayer1.id, - ); - expect(isInMatch2, true); - }); - - // 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), + final playerExists = result.players.any( + (p) => p.id == playerToRemove.id, ); - }, - ); + expect(playerExists, isFalse); + }); - // 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 { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - await database.teamDao.addTeam(team: testTeam1); + test( + 'removePlayerFromMatch() returns false for non-existent player', + () async { + await database.matchDao.addMatch(match: testMatch1); - // Add player to match without team - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, + final removed = await database.playerMatchDao.removePlayerFromMatch( + playerId: 'non-existent-player-id', + matchId: testMatch1.id, + ); + + expect(removed, isFalse); + }, ); - - // Add another player to match with team - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer2.id, - teamId: testTeam1.id, - ); - - // Verify both players are in the match - final isPlayer1InMatch = await database.playerMatchDao.isPlayerInMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - ); - final isPlayer2InMatch = await database.playerMatchDao.isPlayerInMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer2.id, - ); - - expect(isPlayer1InMatch, true); - expect(isPlayer2InMatch, true); - - // Verify only player2 is in the team - final playersInTeam = await database.playerMatchDao.getPlayersInTeam( - matchId: testMatchOnlyGroup.id, - teamId: testTeam1.id, - ); - - expect(playersInTeam.length, 1); - expect(playersInTeam[0].id, testPlayer2.id); - }); - - // Verifies that replaceMatchPlayers removes all existing players and replaces with new list. - test('replaceMatchPlayers replaces all match players correctly', () async { - // Create initial match with 3 players - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - // Verify initial players - var matchPlayers = await database.matchDao.getMatchById( - matchId: testMatchOnlyPlayers.id, - ); - expect(matchPlayers.players.length, 3); - - // Replace with new list containing 2 different players - final newPlayersList = [testPlayer1, testPlayer2]; - await database.matchDao.replaceMatchPlayers( - matchId: testMatchOnlyPlayers.id, - newPlayers: newPlayersList, - ); - - // Get updated match and verify players - matchPlayers = await database.matchDao.getMatchById( - matchId: testMatchOnlyPlayers.id, - ); - - expect(matchPlayers.players.length, 2); - expect(matchPlayers.players.any((p) => p.id == testPlayer1.id), true); - expect(matchPlayers.players.any((p) => p.id == testPlayer2.id), true); - expect(matchPlayers.players.any((p) => p.id == testPlayer4.id), false); - expect(matchPlayers.players.any((p) => p.id == testPlayer5.id), false); - expect(matchPlayers.players.any((p) => p.id == testPlayer6.id), false); }); }); } -- 2.49.1 From ae3a8b496e8436800b7a3b81e0ddf2eae927a10a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 1 May 2026 17:22:20 +0200 Subject: [PATCH 10/15] Added copyWith(), == and hashCode overwrites for model classes --- lib/data/models/game.dart | 39 +++++++++++++++++++- lib/data/models/group.dart | 37 +++++++++++++++++++ lib/data/models/match.dart | 63 +++++++++++++++++++++++++++++--- lib/data/models/player.dart | 32 +++++++++++++++- lib/data/models/score_entry.dart | 20 ++++++++++ lib/data/models/team.dart | 37 +++++++++++++++++-- pubspec.yaml | 1 + 7 files changed, 215 insertions(+), 14 deletions(-) diff --git a/lib/data/models/game.dart b/lib/data/models/game.dart index 607db0a..c02f455 100644 --- a/lib/data/models/game.dart +++ b/lib/data/models/game.dart @@ -28,7 +28,43 @@ class Game { return 'Game{id: $id, name: $name, ruleset: $ruleset, description: $description, color: $color, icon: $icon}'; } - /// Creates a Game instance from a JSON object. + Game copyWith({ + String? id, + DateTime? createdAt, + String? name, + Ruleset? ruleset, + String? description, + GameColor? color, + String? icon, + }) { + return Game( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + name: name ?? this.name, + ruleset: ruleset ?? this.ruleset, + description: description ?? this.description, + color: color ?? this.color, + icon: icon ?? this.icon, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Game && + runtimeType == other.runtimeType && + id == other.id && + createdAt == other.createdAt && + name == other.name && + ruleset == other.ruleset && + description == other.description && + color == other.color && + icon == other.icon; + + @override + int get hashCode => + Object.hash(id, createdAt, name, ruleset, description, color, icon); + Game.fromJson(Map json) : id = json['id'], createdAt = DateTime.parse(json['createdAt']), @@ -41,7 +77,6 @@ class Game { color = GameColor.values.firstWhere((e) => e.name == json['color']), icon = json['icon']; - /// Converts the Game instance to a JSON object. Map toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), diff --git a/lib/data/models/group.dart b/lib/data/models/group.dart index c684541..5c1515c 100644 --- a/lib/data/models/group.dart +++ b/lib/data/models/group.dart @@ -1,4 +1,5 @@ import 'package:clock/clock.dart'; +import 'package:collection/collection.dart'; import 'package:tallee/data/models/player.dart'; import 'package:uuid/uuid.dart'; @@ -24,6 +25,42 @@ class Group { return 'Group{id: $id, name: $name, description: $description, members: $members}'; } + Group copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + List? members, + }) { + return Group( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + members: members ?? this.members, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Group && + runtimeType == other.runtimeType && + id == other.id && + name == other.name && + description == other.description && + createdAt == other.createdAt && + const DeepCollectionEquality().equals(members, other.members); + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + const DeepCollectionEquality().hash(members), + ); + /// Creates a Group instance from a JSON object where the related [Player] /// objects are represented by their IDs. Group.fromJson(Map json) diff --git a/lib/data/models/match.dart b/lib/data/models/match.dart index b0b487c..9d14bb3 100644 --- a/lib/data/models/match.dart +++ b/lib/data/models/match.dart @@ -1,4 +1,5 @@ import 'package:clock/clock.dart'; +import 'package:collection/collection.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/models/game.dart'; import 'package:tallee/data/models/group.dart'; @@ -39,9 +40,62 @@ class Match { return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, mvp: $mvp}'; } - /// Creates a Match instance from a JSON object where related objects are - /// represented by their IDs. Therefore, the game, group, and players are not - /// fully constructed here. + Match copyWith({ + String? id, + DateTime? createdAt, + DateTime? endedAt, + String? name, + Game? game, + Group? group, + List? players, + List? teams, + String? notes, + Map? scores, + }) { + return Match( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + endedAt: endedAt ?? this.endedAt, + name: name ?? this.name, + game: game ?? this.game, + group: group ?? this.group, + players: players ?? this.players, + teams: teams ?? this.teams, + notes: notes ?? this.notes, + scores: scores ?? this.scores, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Match && + runtimeType == other.runtimeType && + id == other.id && + createdAt == other.createdAt && + endedAt == other.endedAt && + name == other.name && + game == other.game && + group == other.group && + const DeepCollectionEquality().equals(players, other.players) && + const DeepCollectionEquality().equals(teams, other.teams) && + notes == other.notes && + const DeepCollectionEquality().equals(scores, other.scores); + + @override + int get hashCode => Object.hash( + id, + createdAt, + endedAt, + name, + game, + group, + const DeepCollectionEquality().hash(players), + const DeepCollectionEquality().hash(teams), + notes, + const DeepCollectionEquality().hash(scores), + ); + Match.fromJson(Map json) : id = json['id'], createdAt = DateTime.parse(json['createdAt']), @@ -71,9 +125,6 @@ class Match { : {}, notes = json['notes'] ?? ''; - /// 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 toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), diff --git a/lib/data/models/player.dart b/lib/data/models/player.dart index 12d17f0..3e42fb9 100644 --- a/lib/data/models/player.dart +++ b/lib/data/models/player.dart @@ -23,7 +23,36 @@ class Player { return 'Player{id: $id, createdAt: $createdAt, name: $name, nameCount: $nameCount, description: $description}'; } - /// Creates a Player instance from a JSON object. + Player copyWith({ + String? id, + DateTime? createdAt, + String? name, + int? nameCount, + String? description, + }) { + return Player( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + name: name ?? this.name, + nameCount: nameCount ?? this.nameCount, + description: description ?? this.description, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Player && + runtimeType == other.runtimeType && + id == other.id && + createdAt == other.createdAt && + name == other.name && + nameCount == other.nameCount && + description == other.description; + + @override + int get hashCode => Object.hash(id, createdAt, name, nameCount, description); + Player.fromJson(Map json) : id = json['id'], createdAt = DateTime.parse(json['createdAt']), @@ -31,7 +60,6 @@ class Player { nameCount = 0, description = json['description']; - /// Converts the Player instance to a JSON object. Map toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), diff --git a/lib/data/models/score_entry.dart b/lib/data/models/score_entry.dart index f9c5ff0..a88f304 100644 --- a/lib/data/models/score_entry.dart +++ b/lib/data/models/score_entry.dart @@ -10,6 +10,26 @@ class ScoreEntry { return 'ScoreEntry{roundNumber: $roundNumber, score: $score, change: $change}'; } + ScoreEntry copyWith({int? roundNumber, int? score, int? change}) { + return ScoreEntry( + roundNumber: roundNumber ?? this.roundNumber, + score: score ?? this.score, + change: change ?? this.change, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ScoreEntry && + runtimeType == other.runtimeType && + roundNumber == other.roundNumber && + score == other.score && + change == other.change; + + @override + int get hashCode => Object.hash(roundNumber, score, change); + ScoreEntry.fromJson(Map json) : roundNumber = json['roundNumber'], score = json['score'], diff --git a/lib/data/models/team.dart b/lib/data/models/team.dart index f5941c4..b16e2ec 100644 --- a/lib/data/models/team.dart +++ b/lib/data/models/team.dart @@ -1,4 +1,5 @@ import 'package:clock/clock.dart'; +import 'package:collection/collection.dart'; import 'package:tallee/data/models/player.dart'; import 'package:uuid/uuid.dart'; @@ -21,16 +22,44 @@ class Team { return 'Team{id: $id, name: $name, members: $members}'; } - /// Creates a Team instance from a JSON object (memberIds format). - /// Player objects are reconstructed from memberIds by the DataTransferService. + Team copyWith({ + String? id, + String? name, + DateTime? createdAt, + List? members, + }) { + return Team( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + members: members ?? this.members, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Team && + runtimeType == other.runtimeType && + id == other.id && + name == other.name && + createdAt == other.createdAt && + const DeepCollectionEquality().equals(members, other.members); + + @override + int get hashCode => Object.hash( + id, + name, + createdAt, + const DeepCollectionEquality().hash(members), + ); + Team.fromJson(Map json) : id = json['id'], name = json['name'], createdAt = DateTime.parse(json['createdAt']), members = []; // Populated during import via DataTransferService - /// Converts the Team instance to a JSON object. Related objects are - /// represented by their IDs. Map toJson() => { 'id': id, 'name': name, diff --git a/pubspec.yaml b/pubspec.yaml index 363ea7f..d5ed039 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: clock: ^1.1.2 + collection: ^1.19.1 cupertino_icons: ^1.0.6 drift: ^2.27.0 drift_flutter: ^0.2.4 -- 2.49.1 From 0f2e3493c4b0297787ba1ade2eb6f36b4b01e9de Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 1 May 2026 17:46:47 +0200 Subject: [PATCH 11/15] Updated player-group dao + tests --- lib/data/dao/group_dao.dart | 44 -- lib/data/dao/player_group_dao.dart | 79 +++- .../group_view/create_group_view.dart | 2 +- test/db_tests/aggregates/group_test.dart | 6 +- .../relationships/player_group_test.dart | 431 +++++++----------- 5 files changed, 228 insertions(+), 334 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 552b138..d1029d0 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -238,48 +238,4 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ); return rowsAffected > 0; } - - /// Replaces all players in a group with the provided list of players. - /// Removes all existing players from the group and adds the new players. - /// Also adds any new players to the player table if they don't exist. - /// Returns `true` if the group exists and players were replaced, `false` otherwise. - Future replaceGroupPlayers({ - required String groupId, - required List newPlayers, - }) async { - if (!await groupExists(groupId: groupId)) return false; - - await db.transaction(() async { - // Remove all existing players from the group - final deleteQuery = delete(db.playerGroupTable) - ..where((p) => p.groupId.equals(groupId)); - await deleteQuery.go(); - - // Add new players to the player table if they don't exist - await Future.wait( - newPlayers.map((player) async { - if (!await db.playerDao.playerExists(playerId: player.id)) { - await db.playerDao.addPlayer(player: player); - } - }), - ); - - // Add the new players to the group - await db.batch( - (b) => b.insertAll( - db.playerGroupTable, - newPlayers - .map( - (player) => PlayerGroupTableCompanion.insert( - playerId: player.id, - groupId: groupId, - ), - ) - .toList(), - mode: InsertMode.insertOrReplace, - ), - ); - }); - return true; - } } diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 9411486..48c5653 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -11,8 +11,7 @@ class PlayerGroupDao extends DatabaseAccessor with _$PlayerGroupDaoMixin { PlayerGroupDao(super.db); - /// No need for a groupHasPlayers method since the members attribute is - /// not nullable + /* Create */ /// Adds a [player] to a group with the given [groupId]. /// If the player is already in the group, no action is taken. @@ -33,10 +32,11 @@ class PlayerGroupDao extends DatabaseAccessor await into(playerGroupTable).insert( PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId), ); - return true; } + /* Read */ + /// Retrieves all players belonging to a specific group by [groupId]. Future> getPlayersOfGroup({required String groupId}) async { final query = select(playerGroupTable) @@ -53,18 +53,6 @@ class PlayerGroupDao extends DatabaseAccessor return groupMembers; } - /// Removes a player from a group based on [playerId] and [groupId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removePlayerFromGroup({ - required String playerId, - required String groupId, - }) async { - final query = delete(playerGroupTable) - ..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId)); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - /// Checks if a player with [playerId] is in the group with [groupId]. /// Returns `true` if the player is in the group, otherwise `false`. Future isPlayerInGroup({ @@ -76,4 +64,65 @@ class PlayerGroupDao extends DatabaseAccessor final result = await query.getSingleOrNull(); return result != null; } + + /* Update */ + + /// Replaces all players in a group with the provided list of players. + /// Removes all existing players from the group and adds the new players. + /// Also adds any new players to the player table if they don't exist. + /// Returns `true` if the group exists and players were replaced, `false` otherwise. + Future replaceGroupPlayers({ + required String groupId, + required List newPlayers, + }) async { + if (!await db.groupDao.groupExists(groupId: groupId)) return false; + if (newPlayers.isEmpty) return false; + + await db.transaction(() async { + // Remove all existing players from the group + final deleteQuery = delete(db.playerGroupTable) + ..where((p) => p.groupId.equals(groupId)); + await deleteQuery.go(); + + // Add new players to the player table if they don't exist + await Future.wait( + newPlayers.map((player) async { + if (!await db.playerDao.playerExists(playerId: player.id)) { + await db.playerDao.addPlayer(player: player); + } + }), + ); + + // Add the new players to the group + await db.batch( + (b) => b.insertAll( + db.playerGroupTable, + newPlayers + .map( + (player) => PlayerGroupTableCompanion.insert( + playerId: player.id, + groupId: groupId, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + }); + return true; + } + + /* Delete */ + + /// Removes a player from a group based on [playerId] and [groupId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removePlayerFromGroup({ + required String playerId, + required String groupId, + }) async { + final query = delete(playerGroupTable) + ..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } } 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 f88e2db..593499e 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 @@ -177,7 +177,7 @@ class _CreateGroupViewState extends State { } if (widget.groupToEdit!.members != selectedPlayers) { - successfullMemberChange = await db.groupDao.replaceGroupPlayers( + successfullMemberChange = await db.playerGroupDao.replaceGroupPlayers( groupId: widget.groupToEdit!.id, newPlayers: selectedPlayers, ); diff --git a/test/db_tests/aggregates/group_test.dart b/test/db_tests/aggregates/group_test.dart index 9040daf..786a260 100644 --- a/test/db_tests/aggregates/group_test.dart +++ b/test/db_tests/aggregates/group_test.dart @@ -306,7 +306,7 @@ void main() { ); final newPlayers = [testPlayer2, testPlayer4]; - final replaced = await database.groupDao.replaceGroupPlayers( + final replaced = await database.playerGroupDao.replaceGroupPlayers( groupId: testGroup1.id, newPlayers: newPlayers, ); @@ -332,7 +332,7 @@ void main() { ); expect(initialGroup.members.length, 3); - final replaced = await database.groupDao.replaceGroupPlayers( + final replaced = await database.playerGroupDao.replaceGroupPlayers( groupId: testGroup1.id, newPlayers: [], ); @@ -348,7 +348,7 @@ void main() { test( 'replaceGroupPlayers() returns false for non-existent group', () async { - final replaced = await database.groupDao.replaceGroupPlayers( + final replaced = await database.playerGroupDao.replaceGroupPlayers( groupId: 'non-existent-id', newPlayers: [testPlayer1], ); diff --git a/test/db_tests/relationships/player_group_test.dart b/test/db_tests/relationships/player_group_test.dart index f687b1c..42d083b 100644 --- a/test/db_tests/relationships/player_group_test.dart +++ b/test/db_tests/relationships/player_group_test.dart @@ -42,189 +42,162 @@ 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); - await database.playerDao.addPlayer(player: testPlayer4); - await database.playerGroupDao.addPlayerToGroup( - groupId: testGroup.id, - player: testPlayer4, - ); - - var playerAdded = await database.playerGroupDao.isPlayerInGroup( - groupId: testGroup.id, - playerId: testPlayer4.id, - ); - - expect(playerAdded, true); - - playerAdded = await database.playerGroupDao.isPlayerInGroup( - groupId: testGroup.id, - playerId: '', - ); - - expect(playerAdded, false); - }); - - // Verifies that a player can be removed from a group and the group's member count decreases. - test('Removing player from group works correctly', () async { - await database.groupDao.addGroup(group: testGroup); - - final playerToRemove = testGroup.members[0]; - - final removed = await database.playerGroupDao.removePlayerFromGroup( - playerId: playerToRemove.id, - groupId: testGroup.id, - ); - expect(removed, true); - - final result = await database.groupDao.getGroupById( - groupId: testGroup.id, - ); - expect(result.members.length, testGroup.members.length - 1); - - final playerExists = result.members.any((p) => p.id == playerToRemove.id); - expect(playerExists, false); - }); - - // Verifies that getPlayersOfGroup returns all members of a group with correct data. - test('Retrieving players of a group works correctly', () async { - await database.groupDao.addGroup(group: testGroup); - final players = await database.playerGroupDao.getPlayersOfGroup( - groupId: testGroup.id, - ); - - for (int i = 0; i < players.length; i++) { - expect(players[i].id, testGroup.members[i].id); - expect(players[i].name, testGroup.members[i].name); - expect(players[i].createdAt, testGroup.members[i].createdAt); - } - }); - - // Verifies that isPlayerInGroup returns false for non-existent player. - test('isPlayerInGroup returns false for non-existent player', () async { - await database.groupDao.addGroup(group: testGroup); - - final result = await database.playerGroupDao.isPlayerInGroup( - playerId: 'non-existent-player-id', - groupId: testGroup.id, - ); - - expect(result, false); - }); - - // Verifies that isPlayerInGroup returns false for non-existent group. - test('isPlayerInGroup returns false for non-existent group', () async { - await database.playerDao.addPlayer(player: testPlayer1); - - final result = await database.playerGroupDao.isPlayerInGroup( - playerId: testPlayer1.id, - groupId: 'non-existent-group-id', - ); - - expect(result, false); - }); - - // Verifies that addPlayerToGroup returns false when player already in group. - test( - 'addPlayerToGroup returns false when player already in group', - () async { + group('CREATE', () { + test('addPlayerToGroup() works correctly', () 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, - ); - - 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); - - // testPlayer4 is not in the database yet - var playerExists = await database.playerDao.playerExists( - playerId: testPlayer4.id, - ); - expect(playerExists, false); - + await database.playerDao.addPlayer(player: testPlayer4); await database.playerGroupDao.addPlayerToGroup( - player: testPlayer4, groupId: testGroup.id, + player: testPlayer4, ); - // Now player should exist in player table - playerExists = await database.playerDao.playerExists( + var playerAdded = await database.playerGroupDao.isPlayerInGroup( + groupId: testGroup.id, playerId: testPlayer4.id, ); - expect(playerExists, true); - }, - ); - // Verifies that removePlayerFromGroup returns false for non-existent player. - test( - 'removePlayerFromGroup returns false for non-existent player', - () async { + expect(playerAdded, true); + }); + + test( + 'addPlayerToGroup() returns false when player already in group', + () async { + await database.groupDao.addGroup(group: testGroup); + + final added = await database.playerGroupDao.addPlayerToGroup( + player: testPlayer1, + groupId: testGroup.id, + ); + expect(added, isFalse); + }, + ); + + test( + 'addPlayerToGroup() adds player to player table if not exists', + () async { + await database.groupDao.addGroup(group: testGroup); + + var playerExists = await database.playerDao.playerExists( + playerId: testPlayer4.id, + ); + expect(playerExists, isFalse); + + await database.playerGroupDao.addPlayerToGroup( + player: testPlayer4, + groupId: testGroup.id, + ); + + playerExists = await database.playerDao.playerExists( + playerId: testPlayer4.id, + ); + expect(playerExists, isTrue); + }, + ); + }); + group('READ', () { + test( + 'isPlayerInGroup() returns false for non-existent player or group', + () async { + await database.groupDao.addGroup(group: testGroup); + + var isInGroup = await database.playerGroupDao.isPlayerInGroup( + playerId: 'non-existent-player-id', + groupId: testGroup.id, + ); + expect(isInGroup, false); + + isInGroup = await database.playerGroupDao.isPlayerInGroup( + playerId: testPlayer1.id, + groupId: 'non-existent-group-id', + ); + expect(isInGroup, false); + + isInGroup = await database.playerGroupDao.isPlayerInGroup( + playerId: 'non-existent-player-id', + groupId: 'non-existent-group-id', + ); + expect(isInGroup, false); + }, + ); + + test('getPlayersOfGroup() works correctly', () async { await database.groupDao.addGroup(group: testGroup); - - final result = await database.playerGroupDao.removePlayerFromGroup( - playerId: 'non-existent-player-id', + final players = await database.playerGroupDao.getPlayersOfGroup( groupId: testGroup.id, ); - expect(result, false); - }, - ); + for (int i = 0; i < players.length; i++) { + expect(players[i].id, testGroup.members[i].id); + expect(players[i].name, testGroup.members[i].name); + expect(players[i].createdAt, testGroup.members[i].createdAt); + } + }); - // 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('getPlayersOfGroup() returns empty list for empty group', () async { + final emptyGroup = Group(name: 'Empty Group', members: []); + await database.groupDao.addGroup(group: emptyGroup); - final result = await database.playerGroupDao.removePlayerFromGroup( - playerId: testPlayer1.id, - groupId: 'non-existent-group-id', + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: emptyGroup.id, ); + expect(players, isEmpty); + }); - 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: [], + test( + 'getPlayersOfGroup() returns empty list for non-existent group', + () async { + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: 'non-existent-group-id', + ); + expect(players, isEmpty); + }, ); - await database.groupDao.addGroup(group: emptyGroup); + }); + group('UPDATE', () { + test('replaceGroupPlayers() works correctly ', () async { + await database.groupDao.addGroup(group: testGroup); - final players = await database.playerGroupDao.getPlayersOfGroup( - groupId: emptyGroup.id, - ); + var groupMembers = await database.groupDao.getGroupById( + groupId: testGroup.id, + ); + expect(groupMembers.members.length, testGroup.members.length); - expect(players, isEmpty); + final newPlayersList = [testPlayer3, testPlayer4]; + + final replaced = await database.playerGroupDao.replaceGroupPlayers( + groupId: testGroup.id, + newPlayers: newPlayersList, + ); + expect(replaced, isTrue); + + groupMembers = await database.groupDao.getGroupById( + groupId: testGroup.id, + ); + expect(groupMembers.members.length, 2); + expect(groupMembers.members.any((p) => p.id == testPlayer3.id), true); + expect(groupMembers.members.any((p) => p.id == testPlayer4.id), true); + }); + }); + group('DELETE', () { + test('removePlayerFromGroup() works correctly', () async { + await database.groupDao.addGroup(group: testGroup); + + final removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: testGroup.id, + ); + expect(removed, true); + + final result = await database.groupDao.getGroupById( + groupId: testGroup.id, + ); + expect(result.members.length, testGroup.members.length - 1); + + final playerExists = result.members.any((p) => p.id == testPlayer1.id); + expect(playerExists, false); + }); }); - // 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', - ); - - 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 { await database.groupDao.addGroup(group: testGroup); @@ -240,137 +213,53 @@ void main() { ); expect(players, isEmpty); - // Group should still exist final groupExists = await database.groupDao.groupExists( groupId: testGroup.id, ); expect(groupExists, true); }); - // 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: [], - ); + test('removePlayerFromGroup() works correctly', () async { await database.groupDao.addGroup(group: testGroup); - await database.groupDao.addGroup(group: secondGroup); - // Add testPlayer1 to second group (already in testGroup) - await database.playerGroupDao.addPlayerToGroup( - player: testPlayer1, - groupId: secondGroup.id, - ); - - final inFirstGroup = await database.playerGroupDao.isPlayerInGroup( + var removed = await database.playerGroupDao.removePlayerFromGroup( playerId: testPlayer1.id, groupId: testGroup.id, ); - final inSecondGroup = await database.playerGroupDao.isPlayerInGroup( + expect(removed, true); + + removed = await database.playerGroupDao.removePlayerFromGroup( playerId: testPlayer1.id, - groupId: secondGroup.id, - ); - - expect(inFirstGroup, true); - expect(inSecondGroup, true); - }); - - // 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); - - // 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, - ); - - expect(inFirstGroup, false); - expect(inSecondGroup, true); - }, - ); - - // Verifies that addPlayerToGroup returns true on successful addition. - test('addPlayerToGroup returns true on successful addition', () async { - await database.groupDao.addGroup(group: testGroup); - await database.playerDao.addPlayer(player: testPlayer4); - - final result = await database.playerGroupDao.addPlayerToGroup( - player: testPlayer4, groupId: testGroup.id, ); - - expect(result, true); + expect(removed, false); }); - // Verifies that removing the same player twice returns false on second attempt. test( - 'Removing same player twice returns false on second attempt', + 'removePlayerFromGroup() returns false for non-existent player or group', () async { await database.groupDao.addGroup(group: testGroup); - final firstRemoval = await database.playerGroupDao - .removePlayerFromGroup( - playerId: testPlayer1.id, - groupId: testGroup.id, - ); - expect(firstRemoval, true); + await database.groupDao.addGroup(group: testGroup); - final secondRemoval = await database.playerGroupDao - .removePlayerFromGroup( - playerId: testPlayer1.id, - groupId: testGroup.id, - ); - expect(secondRemoval, false); + var removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: 'non-existent-player-id', + groupId: testGroup.id, + ); + expect(removed, false); + + removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: 'non-existent-group-id', + ); + expect(removed, false); + + removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: 'non-existent-player-id', + groupId: 'non-existent-group-id', + ); + expect(removed, false); }, ); - - // Verifies that replaceGroupPlayers removes all existing players and replaces with new list. - test('replaceGroupPlayers replaces all group members correctly', () async { - // Create initial group with 3 players - await database.groupDao.addGroup(group: testGroup); - - // Verify initial members - var groupMembers = await database.groupDao.getGroupById( - groupId: testGroup.id, - ); - expect(groupMembers.members.length, 3); - - // Replace with new list containing 2 different players - final newPlayersList = [testPlayer3, testPlayer4]; - await database.groupDao.replaceGroupPlayers( - groupId: testGroup.id, - newPlayers: newPlayersList, - ); - - // Get updated group and verify members - groupMembers = await database.groupDao.getGroupById( - groupId: testGroup.id, - ); - - expect(groupMembers.members.length, 2); - expect(groupMembers.members.any((p) => p.id == testPlayer3.id), true); - expect(groupMembers.members.any((p) => p.id == testPlayer4.id), true); - expect(groupMembers.members.any((p) => p.id == testPlayer1.id), false); - expect(groupMembers.members.any((p) => p.id == testPlayer2.id), false); - }); }); } -- 2.49.1 From ab9a8d01939875dffe44f4740b91a6eeaafb898d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 1 May 2026 18:01:07 +0200 Subject: [PATCH 12/15] Fixed test issues --- lib/data/dao/match_dao.dart | 21 ++++- .../relationships/player_match_test.dart | 6 +- test/services/data_transfer_service_test.dart | 79 +++++++++++-------- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 11cd5a2..f43909c 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -42,11 +42,24 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { await db.teamDao.addTeamsAsList(teams: match.teams!, matchId: match.id); } + // Collect all player IDs that are already in teams + final playersInTeams = {}; + if (match.teams != null) { + for (final team in match.teams!) { + for (final member in team.members) { + playersInTeams.add(member.id); + } + } + } + + // Add players that are not in teams for (final p in match.players) { - await db.playerMatchDao.addPlayerToMatch( - matchId: match.id, - playerId: p.id, - ); + if (!playersInTeams.contains(p.id)) { + await db.playerMatchDao.addPlayerToMatch( + matchId: match.id, + playerId: p.id, + ); + } } for (final pid in match.scores.keys) { diff --git a/test/db_tests/relationships/player_match_test.dart b/test/db_tests/relationships/player_match_test.dart index 2cc5185..85ccab9 100644 --- a/test/db_tests/relationships/player_match_test.dart +++ b/test/db_tests/relationships/player_match_test.dart @@ -103,6 +103,7 @@ void main() { test('addPlayerToMatch() ignores duplicates', () async { await database.matchDao.addMatch(match: testMatch1); + await database.playerDao.addPlayer(player: testPlayer5); final isInMatch = await database.playerMatchDao.isPlayerInMatch( matchId: testMatch1.id, @@ -117,11 +118,11 @@ void main() { await database.playerMatchDao.addPlayerToMatch( matchId: testMatch1.id, - playerId: testPlayer1.id, + playerId: testPlayer5.id, ); await database.playerMatchDao.addPlayerToMatch( matchId: testMatch1.id, - playerId: testPlayer1.id, + playerId: testPlayer5.id, ); players = await database.playerMatchDao.getPlayersOfMatch( @@ -149,6 +150,7 @@ void main() { }); test('isPlayerInMatch() works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); final isInMatch = await database.playerMatchDao.isPlayerInMatch( matchId: testMatch1.id, playerId: testPlayer1.id, diff --git a/test/services/data_transfer_service_test.dart b/test/services/data_transfer_service_test.dart index 6aec390..781ee67 100644 --- a/test/services/data_transfer_service_test.dart +++ b/test/services/data_transfer_service_test.dart @@ -99,10 +99,8 @@ void main() { await database.playerDao.addPlayer(player: testPlayer1); await database.gameDao.addGame(game: testGame); await database.groupDao.addGroup(group: testGroup); - /* - await database.teamDao.addTeam(team: testTeam); -*/ await database.matchDao.addMatch(match: testMatch); + await database.teamDao.addTeam(team: testTeam, matchId: testMatch.id); var playerCount = await database.playerDao.getPlayerCount(); var gameCount = await database.gameDao.getGameCount(); @@ -154,19 +152,16 @@ void main() { 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); }); @@ -179,13 +174,11 @@ void main() { 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); }); }); @@ -247,31 +240,6 @@ void main() { 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], @@ -323,6 +291,51 @@ void main() { expect(player2Score.change, 15); }); + testWidgets('Match with teams is handled correctly', (tester) async { + final matchWithTeams = Match( + name: 'Match with Teams', + game: testGame, + players: [testPlayer1, testPlayer2], + teams: [testTeam], + notes: 'Team match', + ); + + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2], + ); + await database.gameDao.addGame(game: testGame); + await database.matchDao.addMatch(match: matchWithTeams); + + final ctx = await getContext(tester); + final jsonString = await DataTransferService.getAppDataAsJson(ctx); + final decoded = json.decode(jsonString) as Map; + final matches = decoded['matches'] as List; + + expect(matches.length, 1); + + final matchData = matches[0] as Map; + expect(matchData['id'], matchWithTeams.id); + expect(matchData['name'], matchWithTeams.name); + expect( + matchData['teams'], + isNotNull, + reason: 'teams should not be null', + ); + expect(matchData['teams'], isA()); + + final teamsInMatch = matchData['teams'] as List; + expect(teamsInMatch.length, 1); + + final teamData = teamsInMatch[0] as Map; + expect(teamData['id'], testTeam.id); + expect(teamData['name'], testTeam.name); + expect(teamData['memberIds'], isA()); + + final memberIds = teamData['memberIds'] as List; + expect(memberIds.length, 2); + expect(memberIds, containsAll([testPlayer1.id, testPlayer2.id])); + }); + testWidgets('Match without group is handled correctly', (tester) async { final matchWithoutGroup = Match( name: 'No Group Match', -- 2.49.1 From 078daeffc9da7c6e7ac104b38e4d82d1a05d5ea4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 1 May 2026 18:06:09 +0200 Subject: [PATCH 13/15] Fixed test issues --- test/db_tests/aggregates/group_test.dart | 60 +++++++++---------- test/db_tests/entities/game_test.dart | 38 ++++++------ test/db_tests/entities/player_test.dart | 28 ++++----- .../relationships/player_group_test.dart | 28 ++++----- test/db_tests/values/score_entry_test.dart | 32 +++++----- test/services/data_transfer_service_test.dart | 8 +-- 6 files changed, 94 insertions(+), 100 deletions(-) diff --git a/test/db_tests/aggregates/group_test.dart b/test/db_tests/aggregates/group_test.dart index 786a260..2ae1a3f 100644 --- a/test/db_tests/aggregates/group_test.dart +++ b/test/db_tests/aggregates/group_test.dart @@ -99,10 +99,10 @@ void main() { test('addGroup() returns false when group already exists', () async { final firstAdd = await database.groupDao.addGroup(group: testGroup1); - expect(firstAdd, true); + expect(firstAdd, isTrue); final secondAdd = await database.groupDao.addGroup(group: testGroup1); - expect(secondAdd, false); + expect(secondAdd, isFalse); final allGroups = await database.groupDao.getAllGroups(); expect(allGroups.length, 1); @@ -150,14 +150,14 @@ void main() { var groupExists = await database.groupDao.groupExists( groupId: testGroup1.id, ); - expect(groupExists, false); + expect(groupExists, isFalse); await database.groupDao.addGroup(group: testGroup1); groupExists = await database.groupDao.groupExists( groupId: testGroup1.id, ); - expect(groupExists, true); + expect(groupExists, isTrue); }); test('getGroupCount() works correctly', () async { @@ -165,19 +165,19 @@ void main() { expect(count, 0); var added = await database.groupDao.addGroup(group: testGroup1); - expect(added, true); + expect(added, isTrue); count = await database.groupDao.getGroupCount(); expect(count, 1); added = await database.groupDao.addGroup(group: testGroup2); - expect(added, true); + expect(added, isTrue); count = await database.groupDao.getGroupCount(); expect(count, 2); final removed = await database.groupDao.deleteGroup( groupId: testGroup1.id, ); - expect(removed, true); + expect(removed, isTrue); count = await database.groupDao.getGroupCount(); expect(count, 1); }); @@ -225,7 +225,7 @@ void main() { groupId: 'non-existent-id', newName: 'New name', ); - expect(updated, false); + expect(updated, isFalse); }); test('updateGroupDescription() works correctly', () async { @@ -236,7 +236,7 @@ void main() { groupId: testGroup1.id, newDescription: newDescription, ); - expect(updated, true); + expect(updated, isTrue); final group = await database.groupDao.getGroupById( groupId: testGroup1.id, @@ -251,7 +251,7 @@ void main() { groupId: 'non-existent-id', newDescription: 'New description', ); - expect(updated, false); + expect(updated, isFalse); }, ); @@ -288,21 +288,21 @@ void main() { .map((p) => p.id) .toList() .contains(testPlayer1.id), - true, + isTrue, ); expect( initialGroup.members .map((p) => p.id) .toList() .contains(testPlayer2.id), - true, + isTrue, ); expect( initialGroup.members .map((p) => p.id) .toList() .contains(testPlayer3.id), - true, + isTrue, ); final newPlayers = [testPlayer2, testPlayer4]; @@ -310,7 +310,7 @@ void main() { groupId: testGroup1.id, newPlayers: newPlayers, ); - expect(replaced, true); + expect(replaced, isTrue); final updatedGroup = await database.groupDao.getGroupById( groupId: testGroup1.id, @@ -318,31 +318,25 @@ void main() { expect(updatedGroup.members.length, 2); final memberIds = updatedGroup.members.map((p) => p.id).toList(); - expect(memberIds.contains(testPlayer2.id), true); - expect(memberIds.contains(testPlayer4.id), true); - expect(memberIds.contains(testPlayer1.id), false); - expect(memberIds.contains(testPlayer3.id), false); + expect(memberIds.contains(testPlayer2.id), isTrue); + expect(memberIds.contains(testPlayer4.id), isTrue); + expect(memberIds.contains(testPlayer1.id), isFalse); + expect(memberIds.contains(testPlayer3.id), isFalse); }); - test('replaceGroupPlayers() with empty list works correctly', () async { + test('replaceGroupPlayers() ignores empty list ', () async { await database.groupDao.addGroup(group: testGroup1); - final initialGroup = await database.groupDao.getGroupById( - groupId: testGroup1.id, - ); - expect(initialGroup.members.length, 3); - final replaced = await database.playerGroupDao.replaceGroupPlayers( groupId: testGroup1.id, newPlayers: [], ); - expect(replaced, true); + expect(replaced, isFalse); final updatedGroup = await database.groupDao.getGroupById( groupId: testGroup1.id, ); - expect(updatedGroup.members.length, 0); - expect(updatedGroup.members, isEmpty); + expect(updatedGroup.members.length, testGroup1.members.length); }); test( @@ -352,7 +346,7 @@ void main() { groupId: 'non-existent-id', newPlayers: [testPlayer1], ); - expect(replaced, false); + expect(replaced, isFalse); }, ); }); @@ -364,19 +358,19 @@ void main() { final groupDeleted = await database.groupDao.deleteGroup( groupId: testGroup1.id, ); - expect(groupDeleted, true); + expect(groupDeleted, isTrue); final groupExists = await database.groupDao.groupExists( groupId: testGroup1.id, ); - expect(groupExists, false); + expect(groupExists, isFalse); }); test('deleteGroup() returns false for non-existent group', () async { final deleted = await database.groupDao.deleteGroup( groupId: 'non-existent-id', ); - expect(deleted, false); + expect(deleted, isFalse); }); test('deleteAllGroups() works correctly', () async { @@ -388,7 +382,7 @@ void main() { expect(count, 2); final deleted = await database.groupDao.deleteAllGroups(); - expect(deleted, true); + expect(deleted, isTrue); count = await database.groupDao.getGroupCount(); expect(count, 0); @@ -396,7 +390,7 @@ void main() { test('deleteAllGroups() returns false when no groups exist', () async { final deleted = await database.groupDao.deleteAllGroups(); - expect(deleted, false); + expect(deleted, isFalse); }); }); diff --git a/test/db_tests/entities/game_test.dart b/test/db_tests/entities/game_test.dart index f3e99b6..a5af11a 100644 --- a/test/db_tests/entities/game_test.dart +++ b/test/db_tests/entities/game_test.dart @@ -58,7 +58,7 @@ void main() { group('CREATE', () { test('Adding and fetching a single game works correctly', () async { final added = await database.gameDao.addGame(game: testGame1); - expect(added, true); + expect(added, isTrue); final game = await database.gameDao.getGameById(gameId: testGame1.id); expect(game.id, testGame1.id); @@ -74,7 +74,7 @@ void main() { final added = await database.gameDao.addGamesAsList( games: [testGame1, testGame2, testGame3], ); - expect(added, true); + expect(added, isTrue); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 3); @@ -101,7 +101,7 @@ void main() { test('addGamesAsList() returns false for empty list', () async { final result = await database.gameDao.addGamesAsList(games: []); - expect(result, false); + expect(result, isFalse); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 0); @@ -111,7 +111,7 @@ void main() { final added = await database.gameDao.addGamesAsList( games: [testGame1, testGame2, testGame1], ); - expect(added, true); + expect(added, isTrue); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 2); @@ -160,11 +160,11 @@ void main() { test('gameExists() works correctly', () async { var exists = await database.gameDao.gameExists(gameId: testGame1.id); - expect(exists, false); + expect(exists, isFalse); await database.gameDao.addGame(game: testGame1); exists = await database.gameDao.gameExists(gameId: testGame1.id); - expect(exists, true); + expect(exists, isTrue); }); test('getAllGames() returns empty list when no games exist', () async { @@ -199,7 +199,7 @@ void main() { gameId: testGame1.id, newName: newName, ); - expect(updated, true); + expect(updated, isTrue); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, @@ -212,7 +212,7 @@ void main() { gameId: 'non-existent-id', newName: 'New name', ); - expect(updated, false); + expect(updated, isFalse); final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); @@ -226,7 +226,7 @@ void main() { gameId: testGame1.id, newRuleset: ruleset, ); - expect(updated, true); + expect(updated, isTrue); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, @@ -239,7 +239,7 @@ void main() { gameId: 'non-existent-id', newRuleset: Ruleset.lowestScore, ); - expect(updated, false); + expect(updated, isFalse); final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); @@ -253,7 +253,7 @@ void main() { gameId: testGame1.id, newDescription: newDescription, ); - expect(updated, true); + expect(updated, isTrue); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, @@ -268,7 +268,7 @@ void main() { gameId: 'non-existent-id', newDescription: 'New description', ); - expect(updated, false); + expect(updated, isFalse); final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); @@ -294,7 +294,7 @@ void main() { gameId: 'non-existent-id', newColor: GameColor.green, ); - expect(updated, false); + expect(updated, isFalse); final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); @@ -308,7 +308,7 @@ void main() { gameId: testGame1.id, newIcon: newIcon, ); - expect(updated, true); + expect(updated, isTrue); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, @@ -321,7 +321,7 @@ void main() { gameId: 'non-existent-id', newIcon: 'New icon', ); - expect(updated, false); + expect(updated, isFalse); final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); @@ -367,7 +367,7 @@ void main() { await database.gameDao.addGame(game: testGame1); final deleted = await database.gameDao.deleteGame(gameId: testGame1.id); - expect(deleted, true); + expect(deleted, isTrue); final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); @@ -377,7 +377,7 @@ void main() { final deleted = await database.gameDao.deleteGame( gameId: 'non-existent-id', ); - expect(deleted, false); + expect(deleted, isFalse); }); test('deleteAllGames() removes all games', () async { @@ -389,7 +389,7 @@ void main() { expect(count, 3); final deleted = await database.gameDao.deleteAllGames(); - expect(deleted, true); + expect(deleted, isTrue); count = await database.gameDao.getGameCount(); expect(count, 0); @@ -397,7 +397,7 @@ void main() { test('deleteAllGames() returns false when no games exist', () async { final deleted = await database.gameDao.deleteAllGames(); - expect(deleted, false); + expect(deleted, isFalse); }); }); }); diff --git a/test/db_tests/entities/player_test.dart b/test/db_tests/entities/player_test.dart index 963d10e..1e224bc 100644 --- a/test/db_tests/entities/player_test.dart +++ b/test/db_tests/entities/player_test.dart @@ -94,15 +94,15 @@ void main() { test('addPlayer() returns false when player already exists', () async { var added = await database.playerDao.addPlayer(player: testPlayer1); - expect(added, true); + expect(added, isTrue); added = await database.playerDao.addPlayer(player: testPlayer1); - expect(added, false); + expect(added, isFalse); }); test('addPlayersAsList() handles empty list correctly', () async { final added = await database.playerDao.addPlayersAsList(players: []); - expect(added, false); + expect(added, isFalse); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers, isEmpty); @@ -163,13 +163,13 @@ void main() { var playerExists = await database.playerDao.playerExists( playerId: testPlayer1.id, ); - expect(playerExists, false); + expect(playerExists, isFalse); await database.playerDao.addPlayer(player: testPlayer1); playerExists = await database.playerDao.playerExists( playerId: testPlayer1.id, ); - expect(playerExists, true); + expect(playerExists, isTrue); }); test( @@ -227,7 +227,7 @@ void main() { playerId: 'non-existent-id', newName: 'New name', ); - expect(updated, false); + expect(updated, isFalse); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers, isEmpty); @@ -242,7 +242,7 @@ void main() { playerId: testPlayer1.id, newDescription: newDescription, ); - expect(updated, true); + expect(updated, isTrue); final player = await database.playerDao.getPlayerById( playerId: testPlayer1.id, @@ -257,7 +257,7 @@ void main() { playerId: 'non-existent-id', newDescription: 'New description', ); - expect(updated, false); + expect(updated, isFalse); final allPlayers = await database.playerDao.getAllPlayers(); expect(allPlayers, isEmpty); @@ -305,19 +305,19 @@ void main() { final playerDeleted = await database.playerDao.deletePlayer( playerId: testPlayer1.id, ); - expect(playerDeleted, true); + expect(playerDeleted, isTrue); final playerExists = await database.playerDao.playerExists( playerId: testPlayer1.id, ); - expect(playerExists, false); + expect(playerExists, isFalse); }); test('deletePlayer() returns false for non-existent player', () async { final deleted = await database.playerDao.deletePlayer( playerId: 'non-existent-id', ); - expect(deleted, false); + expect(deleted, isFalse); }); test('deleteAllPlayers() removes all players', () async { @@ -329,7 +329,7 @@ void main() { expect(playerCount, 3); final deleted = await database.playerDao.deleteAllPlayers(); - expect(deleted, true); + expect(deleted, isTrue); playerCount = await database.playerDao.getPlayerCount(); expect(playerCount, 0); @@ -337,7 +337,7 @@ void main() { test('deleteAllPlayers() returns false when no players exist', () async { final deleted = await database.playerDao.deleteAllPlayers(); - expect(deleted, false); + expect(deleted, isFalse); }); }); @@ -429,7 +429,7 @@ void main() { playerId: testPlayer1.id, nameCount: 2, ); - expect(success, true); + expect(success, isTrue); final player = await database.playerDao.getPlayerById( playerId: testPlayer1.id, diff --git a/test/db_tests/relationships/player_group_test.dart b/test/db_tests/relationships/player_group_test.dart index 42d083b..70e3d30 100644 --- a/test/db_tests/relationships/player_group_test.dart +++ b/test/db_tests/relationships/player_group_test.dart @@ -56,7 +56,7 @@ void main() { playerId: testPlayer4.id, ); - expect(playerAdded, true); + expect(playerAdded, isTrue); }); test( @@ -104,19 +104,19 @@ void main() { playerId: 'non-existent-player-id', groupId: testGroup.id, ); - expect(isInGroup, false); + expect(isInGroup, isFalse); isInGroup = await database.playerGroupDao.isPlayerInGroup( playerId: testPlayer1.id, groupId: 'non-existent-group-id', ); - expect(isInGroup, false); + expect(isInGroup, isFalse); isInGroup = await database.playerGroupDao.isPlayerInGroup( playerId: 'non-existent-player-id', groupId: 'non-existent-group-id', ); - expect(isInGroup, false); + expect(isInGroup, isFalse); }, ); @@ -174,8 +174,8 @@ void main() { groupId: testGroup.id, ); expect(groupMembers.members.length, 2); - expect(groupMembers.members.any((p) => p.id == testPlayer3.id), true); - expect(groupMembers.members.any((p) => p.id == testPlayer4.id), true); + expect(groupMembers.members.any((p) => p.id == testPlayer3.id), isTrue); + expect(groupMembers.members.any((p) => p.id == testPlayer4.id), isTrue); }); }); group('DELETE', () { @@ -186,7 +186,7 @@ void main() { playerId: testPlayer1.id, groupId: testGroup.id, ); - expect(removed, true); + expect(removed, isTrue); final result = await database.groupDao.getGroupById( groupId: testGroup.id, @@ -194,7 +194,7 @@ void main() { expect(result.members.length, testGroup.members.length - 1); final playerExists = result.members.any((p) => p.id == testPlayer1.id); - expect(playerExists, false); + expect(playerExists, isFalse); }); }); @@ -216,7 +216,7 @@ void main() { final groupExists = await database.groupDao.groupExists( groupId: testGroup.id, ); - expect(groupExists, true); + expect(groupExists, isTrue); }); test('removePlayerFromGroup() works correctly', () async { @@ -226,13 +226,13 @@ void main() { playerId: testPlayer1.id, groupId: testGroup.id, ); - expect(removed, true); + expect(removed, isTrue); removed = await database.playerGroupDao.removePlayerFromGroup( playerId: testPlayer1.id, groupId: testGroup.id, ); - expect(removed, false); + expect(removed, isFalse); }); test( @@ -246,19 +246,19 @@ void main() { playerId: 'non-existent-player-id', groupId: testGroup.id, ); - expect(removed, false); + expect(removed, isFalse); removed = await database.playerGroupDao.removePlayerFromGroup( playerId: testPlayer1.id, groupId: 'non-existent-group-id', ); - expect(removed, false); + expect(removed, isFalse); removed = await database.playerGroupDao.removePlayerFromGroup( playerId: 'non-existent-player-id', groupId: 'non-existent-group-id', ); - expect(removed, false); + expect(removed, isFalse); }, ); }); diff --git a/test/db_tests/values/score_entry_test.dart b/test/db_tests/values/score_entry_test.dart index bb41a9a..a4b5df4 100644 --- a/test/db_tests/values/score_entry_test.dart +++ b/test/db_tests/values/score_entry_test.dart @@ -259,7 +259,7 @@ void main() { matchId: testMatch1.id, ); - expect(scores.isEmpty, true); + expect(scores.isEmpty, isTrue); }); test('getAllPlayerScoresInMatch() works correctly', () async { @@ -299,7 +299,7 @@ void main() { matchId: testMatch1.id, ); - expect(playerScores.isEmpty, true); + expect(playerScores.isEmpty, isTrue); }); test('Scores are isolated across different matches', () async { @@ -438,7 +438,7 @@ void main() { newEntry: newEntry, ); - expect(updated, true); + expect(updated, isTrue); final score = await database.scoreEntryDao.getScore( playerId: testPlayer1.id, @@ -458,7 +458,7 @@ void main() { newEntry: entryRound1, ); - expect(updated, false); + expect(updated, isFalse); }); }); @@ -476,7 +476,7 @@ void main() { roundNumber: 1, ); - expect(deleted, true); + expect(deleted, isTrue); final score = await database.scoreEntryDao.getScore( playerId: testPlayer1.id, @@ -494,7 +494,7 @@ void main() { roundNumber: 1, ); - expect(deleted, false); + expect(deleted, isFalse); }); test('deleteAllScoresForMatch() works correctly', () async { @@ -523,7 +523,7 @@ void main() { matchId: testMatch1.id, ); - expect(deleted, true); + expect(deleted, isTrue); final match1Scores = await database.scoreEntryDao.getAllMatchScores( matchId: testMatch1.id, @@ -555,7 +555,7 @@ void main() { matchId: testMatch1.id, ); - expect(deleted, true); + expect(deleted, isTrue); final player1Scores = await database.scoreEntryDao .getAllPlayerScoresInMatch( @@ -578,7 +578,7 @@ void main() { var hasWinner = await database.scoreEntryDao.hasWinner( matchId: testMatch1.id, ); - expect(hasWinner, false); + expect(hasWinner, isFalse); await database.scoreEntryDao.setWinner( playerId: testPlayer1.id, @@ -588,7 +588,7 @@ void main() { hasWinner = await database.scoreEntryDao.hasWinner( matchId: testMatch1.id, ); - expect(hasWinner, true); + expect(hasWinner, isTrue); }); test('getWinnersForMatch() returns correct winner', () async { @@ -612,7 +612,7 @@ void main() { var removed = await database.scoreEntryDao.removeWinner( matchId: testMatch1.id, ); - expect(removed, false); + expect(removed, isFalse); await database.scoreEntryDao.setWinner( playerId: testPlayer1.id, @@ -622,7 +622,7 @@ void main() { removed = await database.scoreEntryDao.removeWinner( matchId: testMatch1.id, ); - expect(removed, true); + expect(removed, isTrue); var winner = await database.scoreEntryDao.getWinner( matchId: testMatch1.id, @@ -636,7 +636,7 @@ void main() { var hasLooser = await database.scoreEntryDao.hasLoser( matchId: testMatch1.id, ); - expect(hasLooser, false); + expect(hasLooser, isFalse); await database.scoreEntryDao.setLoser( playerId: testPlayer1.id, @@ -646,7 +646,7 @@ void main() { hasLooser = await database.scoreEntryDao.hasLoser( matchId: testMatch1.id, ); - expect(hasLooser, true); + expect(hasLooser, isTrue); }); test('getLoser() returns correct winner', () async { @@ -670,7 +670,7 @@ void main() { var removed = await database.scoreEntryDao.removeLoser( matchId: testMatch1.id, ); - expect(removed, false); + expect(removed, isFalse); await database.scoreEntryDao.setLoser( playerId: testPlayer1.id, @@ -680,7 +680,7 @@ void main() { removed = await database.scoreEntryDao.removeLoser( matchId: testMatch1.id, ); - expect(removed, true); + expect(removed, isTrue); var looser = await database.scoreEntryDao.getLoser( matchId: testMatch1.id, diff --git a/test/services/data_transfer_service_test.dart b/test/services/data_transfer_service_test.dart index 781ee67..fec70b7 100644 --- a/test/services/data_transfer_service_test.dart +++ b/test/services/data_transfer_service_test.dart @@ -149,10 +149,10 @@ void main() { 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('matches'), true); + expect(decoded.containsKey('players'), isTrue); + expect(decoded.containsKey('games'), isTrue); + expect(decoded.containsKey('groups'), isTrue); + expect(decoded.containsKey('matches'), isTrue); final players = decoded['players'] as List; final games = decoded['games'] as List; -- 2.49.1 From 9e4f44491cea776cb9557be5f857b6e5a6a5c3f1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 2 May 2026 01:28:00 +0200 Subject: [PATCH 14/15] Made match notes non nullable --- lib/data/dao/match_dao.dart | 10 +++---- lib/data/db/database.g.dart | 43 ++++++++++++++--------------- lib/data/db/tables/match_table.dart | 2 +- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index f43909c..6959b1b 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -30,7 +30,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { gameId: match.game.id, groupId: Value(match.group?.id), name: match.name, - notes: Value(match.notes), + notes: match.notes, createdAt: match.createdAt, endedAt: Value(match.endedAt), ), @@ -142,7 +142,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { gameId: match.game.id, groupId: Value(match.group?.id), name: match.name, - notes: Value(match.notes), + notes: match.notes, createdAt: match.createdAt, endedAt: Value(match.endedAt), ), @@ -300,7 +300,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { group: group, players: players, teams: teams.isEmpty ? null : teams, - notes: row.notes ?? '', + notes: row.notes, createdAt: row.createdAt, endedAt: row.endedAt, scores: scores, @@ -334,7 +334,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { group: group, players: players, teams: teams.isEmpty ? null : teams, - notes: result.notes ?? '', + notes: result.notes, createdAt: result.createdAt, endedAt: result.endedAt, scores: scores, @@ -362,7 +362,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { group: group, players: players, teams: teams.isEmpty ? null : teams, - notes: row.notes ?? '', + notes: row.notes, createdAt: row.createdAt, endedAt: row.endedAt, ); diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index b4f3702..c8d0faa 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -1190,9 +1190,9 @@ class $MatchTableTable extends MatchTable late final GeneratedColumn notes = GeneratedColumn( 'notes', aliasedName, - true, + false, type: DriftSqlType.string, - requiredDuringInsert: false, + requiredDuringInsert: true, ); static const VerificationMeta _createdAtMeta = const VerificationMeta( 'createdAt', @@ -1270,6 +1270,8 @@ class $MatchTableTable extends MatchTable _notesMeta, notes.isAcceptableOrUnknown(data['notes']!, _notesMeta), ); + } else if (isInserting) { + context.missing(_notesMeta); } if (data.containsKey('created_at')) { context.handle( @@ -1313,7 +1315,7 @@ class $MatchTableTable extends MatchTable notes: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}notes'], - ), + )!, createdAt: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}created_at'], @@ -1336,7 +1338,7 @@ class MatchTableData extends DataClass implements Insertable { final String gameId; final String? groupId; final String name; - final String? notes; + final String notes; final DateTime createdAt; final DateTime? endedAt; const MatchTableData({ @@ -1344,7 +1346,7 @@ class MatchTableData extends DataClass implements Insertable { required this.gameId, this.groupId, required this.name, - this.notes, + required this.notes, required this.createdAt, this.endedAt, }); @@ -1357,9 +1359,7 @@ class MatchTableData extends DataClass implements Insertable { map['group_id'] = Variable(groupId); } map['name'] = Variable(name); - if (!nullToAbsent || notes != null) { - map['notes'] = Variable(notes); - } + map['notes'] = Variable(notes); map['created_at'] = Variable(createdAt); if (!nullToAbsent || endedAt != null) { map['ended_at'] = Variable(endedAt); @@ -1375,9 +1375,7 @@ class MatchTableData extends DataClass implements Insertable { ? const Value.absent() : Value(groupId), name: Value(name), - notes: notes == null && nullToAbsent - ? const Value.absent() - : Value(notes), + notes: Value(notes), createdAt: Value(createdAt), endedAt: endedAt == null && nullToAbsent ? const Value.absent() @@ -1395,7 +1393,7 @@ class MatchTableData extends DataClass implements Insertable { gameId: serializer.fromJson(json['gameId']), groupId: serializer.fromJson(json['groupId']), name: serializer.fromJson(json['name']), - notes: serializer.fromJson(json['notes']), + notes: serializer.fromJson(json['notes']), createdAt: serializer.fromJson(json['createdAt']), endedAt: serializer.fromJson(json['endedAt']), ); @@ -1408,7 +1406,7 @@ class MatchTableData extends DataClass implements Insertable { 'gameId': serializer.toJson(gameId), 'groupId': serializer.toJson(groupId), 'name': serializer.toJson(name), - 'notes': serializer.toJson(notes), + 'notes': serializer.toJson(notes), 'createdAt': serializer.toJson(createdAt), 'endedAt': serializer.toJson(endedAt), }; @@ -1419,7 +1417,7 @@ class MatchTableData extends DataClass implements Insertable { String? gameId, Value groupId = const Value.absent(), String? name, - Value notes = const Value.absent(), + String? notes, DateTime? createdAt, Value endedAt = const Value.absent(), }) => MatchTableData( @@ -1427,7 +1425,7 @@ class MatchTableData extends DataClass implements Insertable { gameId: gameId ?? this.gameId, groupId: groupId.present ? groupId.value : this.groupId, name: name ?? this.name, - notes: notes.present ? notes.value : this.notes, + notes: notes ?? this.notes, createdAt: createdAt ?? this.createdAt, endedAt: endedAt.present ? endedAt.value : this.endedAt, ); @@ -1478,7 +1476,7 @@ class MatchTableCompanion extends UpdateCompanion { final Value gameId; final Value groupId; final Value name; - final Value notes; + final Value notes; final Value createdAt; final Value endedAt; final Value rowid; @@ -1497,13 +1495,14 @@ class MatchTableCompanion extends UpdateCompanion { required String gameId, this.groupId = const Value.absent(), required String name, - this.notes = const Value.absent(), + required String notes, required DateTime createdAt, this.endedAt = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), gameId = Value(gameId), name = Value(name), + notes = Value(notes), createdAt = Value(createdAt); static Insertable custom({ Expression? id, @@ -1532,7 +1531,7 @@ class MatchTableCompanion extends UpdateCompanion { Value? gameId, Value? groupId, Value? name, - Value? notes, + Value? notes, Value? createdAt, Value? endedAt, Value? rowid, @@ -4093,7 +4092,7 @@ typedef $$MatchTableTableCreateCompanionBuilder = required String gameId, Value groupId, required String name, - Value notes, + required String notes, required DateTime createdAt, Value endedAt, Value rowid, @@ -4104,7 +4103,7 @@ typedef $$MatchTableTableUpdateCompanionBuilder = Value gameId, Value groupId, Value name, - Value notes, + Value notes, Value createdAt, Value endedAt, Value rowid, @@ -4567,7 +4566,7 @@ class $$MatchTableTableTableManager Value gameId = const Value.absent(), Value groupId = const Value.absent(), Value name = const Value.absent(), - Value notes = const Value.absent(), + Value notes = const Value.absent(), Value createdAt = const Value.absent(), Value endedAt = const Value.absent(), Value rowid = const Value.absent(), @@ -4587,7 +4586,7 @@ class $$MatchTableTableTableManager required String gameId, Value groupId = const Value.absent(), required String name, - Value notes = const Value.absent(), + required String notes, required DateTime createdAt, Value endedAt = const Value.absent(), Value rowid = const Value.absent(), diff --git a/lib/data/db/tables/match_table.dart b/lib/data/db/tables/match_table.dart index 25b0a73..c565547 100644 --- a/lib/data/db/tables/match_table.dart +++ b/lib/data/db/tables/match_table.dart @@ -12,7 +12,7 @@ class MatchTable extends Table { .references(GroupTable, #id, onDelete: KeyAction.setNull) .nullable()(); TextColumn get name => text()(); - TextColumn get notes => text().nullable()(); + TextColumn get notes => text()(); DateTimeColumn get createdAt => dateTime()(); DateTimeColumn get endedAt => dateTime().nullable()(); -- 2.49.1 From 5789650c973e56ebf47213a1e78b592dcad7cd2b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 2 May 2026 01:31:49 +0200 Subject: [PATCH 15/15] Refactoring --- lib/data/dao/game_dao.dart | 22 ++++++++-------- lib/data/dao/group_dao.dart | 12 ++++----- lib/data/dao/match_dao.dart | 12 ++++----- lib/data/dao/player_dao.dart | 16 ++++++------ lib/data/dao/player_match_dao.dart | 8 +++--- lib/data/dao/score_entry_dao.dart | 8 +++--- lib/data/dao/team_dao.dart | 4 +-- .../group_view/create_group_view.dart | 2 +- .../create_match/create_match_view.dart | 4 +-- test/db_tests/aggregates/group_test.dart | 12 ++++----- test/db_tests/aggregates/match_test.dart | 10 +++---- test/db_tests/aggregates/team_test.dart | 4 +-- test/db_tests/entities/game_test.dart | 26 +++++++++---------- test/db_tests/entities/player_test.dart | 14 +++++----- .../relationships/player_match_test.dart | 8 +++--- test/db_tests/values/score_entry_test.dart | 4 +-- 16 files changed, 83 insertions(+), 83 deletions(-) diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart index 099c501..400f04a 100644 --- a/lib/data/dao/game_dao.dart +++ b/lib/data/dao/game_dao.dart @@ -117,14 +117,14 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /* Update */ - /// Updates the name of the game with the given [gameId] to [newName]. + /// Updates the name of the game with the given [gameId] to [name]. Future updateGameName({ required String gameId, - required String newName, + required String name, }) async { final rowsAffected = await (update(gameTable)..where((g) => g.id.equals(gameId))).write( - GameTableCompanion(name: Value(newName)), + GameTableCompanion(name: Value(name)), ); return rowsAffected > 0; } @@ -132,11 +132,11 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Updates the ruleset of the game with the given [gameId]. Future updateGameRuleset({ required String gameId, - required Ruleset newRuleset, + required Ruleset ruleset, }) async { final rowsAffected = await (update(gameTable)..where((g) => g.id.equals(gameId))).write( - GameTableCompanion(ruleset: Value(newRuleset.name)), + GameTableCompanion(ruleset: Value(ruleset.name)), ); return rowsAffected > 0; } @@ -144,11 +144,11 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Updates the description of the game with the given [gameId]. Future updateGameDescription({ required String gameId, - required String newDescription, + required String description, }) async { final rowsAffected = await (update(gameTable)..where((g) => g.id.equals(gameId))).write( - GameTableCompanion(description: Value(newDescription)), + GameTableCompanion(description: Value(description)), ); return rowsAffected > 0; } @@ -156,11 +156,11 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Updates the color of the game with the given [gameId]. Future updateGameColor({ required String gameId, - required GameColor newColor, + required GameColor color, }) async { final rowsAffected = await (update(gameTable)..where((g) => g.id.equals(gameId))).write( - GameTableCompanion(color: Value(newColor.name)), + GameTableCompanion(color: Value(color.name)), ); return rowsAffected > 0; } @@ -168,11 +168,11 @@ class GameDao extends DatabaseAccessor with _$GameDaoMixin { /// Updates the icon of the game with the given [gameId]. Future updateGameIcon({ required String gameId, - required String newIcon, + required String icon, }) async { final rowsAffected = await (update(gameTable)..where((g) => g.id.equals(gameId))).write( - GameTableCompanion(icon: Value(newIcon)), + GameTableCompanion(icon: Value(icon)), ); return rowsAffected > 0; } diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index d1029d0..bffe5a4 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -213,28 +213,28 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { /* Update */ - /// Updates the name of the group with the given [id] to [newName]. + /// Updates the name of the group with the given [id] to [name]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updateGroupName({ required String groupId, - required String newName, + required String name, }) async { final rowsAffected = await (update(groupTable)..where((g) => g.id.equals(groupId))).write( - GroupTableCompanion(name: Value(newName)), + GroupTableCompanion(name: Value(name)), ); return rowsAffected > 0; } - /// Updates the description of the group with the given [groupId] to [newDescription]. + /// Updates the description of the group with the given [groupId] to [description]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updateGroupDescription({ required String groupId, - required String newDescription, + required String description, }) async { final rowsAffected = await (update(groupTable)..where((g) => g.id.equals(groupId))).write( - GroupTableCompanion(description: Value(newDescription)), + GroupTableCompanion(description: Value(description)), ); return rowsAffected > 0; } diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 6959b1b..69aaeef 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -395,30 +395,30 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /* Update */ - /// Changes the name of the match with the given [matchId] to [newName]. + /// Changes the name of the match with the given [matchId] to [name]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updateMatchName({ required String matchId, - required String newName, + required String name, }) async { final query = update(matchTable)..where((g) => g.id.equals(matchId)); final rowsAffected = await query.write( - MatchTableCompanion(name: Value(newName)), + MatchTableCompanion(name: Value(name)), ); return rowsAffected > 0; } /// Updates the group of the match with the given [matchId]. - /// Replaces the existing group association with the new group specified by [newGroupId]. + /// Replaces the existing group association with the new group specified by [groupId]. /// Pass null to remove the group association. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updateMatchGroup({ required String matchId, - required String? newGroupId, + required String? groupId, }) async { final query = update(matchTable)..where((g) => g.id.equals(matchId)); final rowsAffected = await query.write( - MatchTableCompanion(groupId: Value(newGroupId)), + MatchTableCompanion(groupId: Value(groupId)), ); return rowsAffected > 0; } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index dd46e17..51e5845 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -158,10 +158,10 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { /* Update */ - /// Updates the name of the player with the given [playerId] to [newName]. + /// Updates the name of the player with the given [playerId] to [name]. Future updatePlayerName({ required String playerId, - required String newName, + required String name, }) async { // Get previous name and name count for the player before updating final previousPlayerName = @@ -173,13 +173,13 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { final rowsAffected = await (update(playerTable)..where((p) => p.id.equals(playerId))).write( - PlayerTableCompanion(name: Value(newName)), + PlayerTableCompanion(name: Value(name)), ); // Update name count for the new name - final count = await calculateNameCount(name: newName); + final count = await calculateNameCount(name: name); if (count > 0) { - await (update(playerTable)..where((p) => p.name.equals(newName))).write( + await (update(playerTable)..where((p) => p.name.equals(name))).write( PlayerTableCompanion(nameCount: Value(count)), ); } @@ -200,15 +200,15 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { } /// Updates the description of the player with the given [playerId] to - /// [newDescription]. + /// [description]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future updatePlayerDescription({ required String playerId, - required String newDescription, + required String description, }) async { final rowsAffected = await (update(playerTable)..where((g) => g.id.equals(playerId))).write( - PlayerTableCompanion(description: Value(newDescription)), + PlayerTableCompanion(description: Value(description)), ); return rowsAffected > 0; } diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index 1c9d0dd..d119468 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -116,18 +116,18 @@ class PlayerMatchDao extends DatabaseAccessor } /// Updates the players associated with a match based on the provided - /// [newPlayer] list. It adds new players and removes players that are no + /// [player] list. It adds new players and removes players that are no /// longer associated with the match. Future updateMatchPlayers({ required String matchId, - required List newPlayer, + required List player, }) async { - if (newPlayer.isEmpty) return false; + if (player.isEmpty) return false; final currentPlayers = await getPlayersOfMatch(matchId: matchId); // Create sets of player IDs for easy comparison final currentPlayerIds = currentPlayers.map((p) => p.id).toSet(); - final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet(); + final newPlayerIdsSet = player.map((p) => p.id).toSet(); // Are the current and new player identical? if (currentPlayerIds.containsAll(newPlayerIdsSet) && diff --git a/lib/data/dao/score_entry_dao.dart b/lib/data/dao/score_entry_dao.dart index 0d04e33..9c4e01d 100644 --- a/lib/data/dao/score_entry_dao.dart +++ b/lib/data/dao/score_entry_dao.dart @@ -162,19 +162,19 @@ class ScoreEntryDao extends DatabaseAccessor Future updateScore({ required String playerId, required String matchId, - required ScoreEntry newEntry, + required ScoreEntry entry, }) async { final rowsAffected = await (update(scoreEntryTable)..where( (s) => s.playerId.equals(playerId) & s.matchId.equals(matchId) & - s.roundNumber.equals(newEntry.roundNumber), + s.roundNumber.equals(entry.roundNumber), )) .write( ScoreEntryTableCompanion( - score: Value(newEntry.score), - change: Value(newEntry.change), + score: Value(entry.score), + change: Value(entry.change), ), ); return rowsAffected > 0; diff --git a/lib/data/dao/team_dao.dart b/lib/data/dao/team_dao.dart index 708b475..cba68fb 100644 --- a/lib/data/dao/team_dao.dart +++ b/lib/data/dao/team_dao.dart @@ -153,11 +153,11 @@ class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { /// Updates the name of the team with the given [teamId]. Future updateTeamName({ required String teamId, - required String newName, + required String name, }) async { final rowsAffected = await (update(teamTable)..where((t) => t.id.equals(teamId))).write( - TeamTableCompanion(name: Value(newName)), + TeamTableCompanion(name: Value(name)), ); return rowsAffected > 0; } 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 593499e..3a2ee60 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 @@ -172,7 +172,7 @@ class _CreateGroupViewState extends State { if (widget.groupToEdit!.name != groupName) { successfullNameChange = await db.groupDao.updateGroupName( groupId: widget.groupToEdit!.id, - newName: groupName, + name: groupName, ); } 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 1a04c78..cb26de8 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 @@ -271,14 +271,14 @@ class _CreateMatchViewState extends State { if (widget.matchToEdit!.name != updatedMatch.name) { await db.matchDao.updateMatchName( matchId: widget.matchToEdit!.id, - newName: updatedMatch.name, + name: updatedMatch.name, ); } if (widget.matchToEdit!.group?.id != updatedMatch.group?.id) { await db.matchDao.updateMatchGroup( matchId: widget.matchToEdit!.id, - newGroupId: updatedMatch.group?.id, + groupId: updatedMatch.group?.id, ); } diff --git a/test/db_tests/aggregates/group_test.dart b/test/db_tests/aggregates/group_test.dart index 2ae1a3f..1498523 100644 --- a/test/db_tests/aggregates/group_test.dart +++ b/test/db_tests/aggregates/group_test.dart @@ -211,7 +211,7 @@ void main() { const newName = 'New name'; await database.groupDao.updateGroupName( groupId: testGroup1.id, - newName: newName, + name: newName, ); final result = await database.groupDao.getGroupById( @@ -223,7 +223,7 @@ void main() { test('updateGroupName() returns false for non-existent group', () async { final updated = await database.groupDao.updateGroupName( groupId: 'non-existent-id', - newName: 'New name', + name: 'New name', ); expect(updated, isFalse); }); @@ -234,7 +234,7 @@ void main() { const newDescription = 'New description'; final updated = await database.groupDao.updateGroupDescription( groupId: testGroup1.id, - newDescription: newDescription, + description: newDescription, ); expect(updated, isTrue); @@ -249,7 +249,7 @@ void main() { () async { final updated = await database.groupDao.updateGroupDescription( groupId: 'non-existent-id', - newDescription: 'New description', + description: 'New description', ); expect(updated, isFalse); }, @@ -262,11 +262,11 @@ void main() { await database.groupDao.updateGroupName( groupId: testGroup1.id, - newName: newName, + name: newName, ); await database.groupDao.updateGroupDescription( groupId: testGroup1.id, - newDescription: newDescription, + description: newDescription, ); final updatedGroup = await database.groupDao.getGroupById( diff --git a/test/db_tests/aggregates/match_test.dart b/test/db_tests/aggregates/match_test.dart index a056b2c..fccecfe 100644 --- a/test/db_tests/aggregates/match_test.dart +++ b/test/db_tests/aggregates/match_test.dart @@ -268,7 +268,7 @@ void main() { const newName = 'New name'; await database.matchDao.updateMatchName( matchId: testMatch1.id, - newName: newName, + name: newName, ); final fetchedMatch = await database.matchDao.getMatchById( @@ -280,7 +280,7 @@ void main() { test('updateMatchName() does nothing for non-existent match', () async { final updated = await database.matchDao.updateMatchName( matchId: 'non-existing-id', - newName: 'New Name', + name: 'New Name', ); expect(updated, isFalse); @@ -294,7 +294,7 @@ void main() { await database.matchDao.updateMatchGroup( matchId: testMatch1.id, - newGroupId: testGroup2.id, + groupId: testGroup2.id, ); final fetchedMatch = await database.matchDao.getMatchById( @@ -306,7 +306,7 @@ void main() { test('updateMatchGroup() does nothing for non-existent match', () async { final updated = await database.matchDao.updateMatchGroup( matchId: 'non-existing-id', - newGroupId: 'group-id', + groupId: 'group-id', ); expect(updated, isFalse); @@ -362,7 +362,7 @@ void main() { const newName = 'New name'; await database.matchDao.updateMatchName( matchId: testMatch1.id, - newName: newName, + name: newName, ); final fetchedMatch = await database.matchDao.getMatchById( diff --git a/test/db_tests/aggregates/team_test.dart b/test/db_tests/aggregates/team_test.dart index 592810d..fefdcc5 100644 --- a/test/db_tests/aggregates/team_test.dart +++ b/test/db_tests/aggregates/team_test.dart @@ -263,7 +263,7 @@ void main() { const newName = 'New name'; await database.teamDao.updateTeamName( teamId: testTeam1.id, - newName: newName, + name: newName, ); fetchedTeam = await database.teamDao.getTeamById(teamId: testTeam1.id); @@ -273,7 +273,7 @@ void main() { test('updateTeamName() does nothing for non-existent team', () async { final updated = await database.teamDao.updateTeamName( teamId: 'non-existing-id', - newName: 'New Name', + name: 'New Name', ); expect(updated, isFalse); diff --git a/test/db_tests/entities/game_test.dart b/test/db_tests/entities/game_test.dart index a5af11a..778d43b 100644 --- a/test/db_tests/entities/game_test.dart +++ b/test/db_tests/entities/game_test.dart @@ -197,7 +197,7 @@ void main() { final updated = await database.gameDao.updateGameName( gameId: testGame1.id, - newName: newName, + name: newName, ); expect(updated, isTrue); @@ -210,7 +210,7 @@ void main() { test('updateGameName() does nothing for non-existent game', () async { final updated = await database.gameDao.updateGameName( gameId: 'non-existent-id', - newName: 'New name', + name: 'New name', ); expect(updated, isFalse); @@ -224,7 +224,7 @@ void main() { final updated = await database.gameDao.updateGameRuleset( gameId: testGame1.id, - newRuleset: ruleset, + ruleset: ruleset, ); expect(updated, isTrue); @@ -237,7 +237,7 @@ void main() { test('updateGameRuleset() does nothing for non-existent game', () async { final updated = await database.gameDao.updateGameRuleset( gameId: 'non-existent-id', - newRuleset: Ruleset.lowestScore, + ruleset: Ruleset.lowestScore, ); expect(updated, isFalse); @@ -251,7 +251,7 @@ void main() { final updated = await database.gameDao.updateGameDescription( gameId: testGame1.id, - newDescription: newDescription, + description: newDescription, ); expect(updated, isTrue); @@ -266,7 +266,7 @@ void main() { () async { final updated = await database.gameDao.updateGameDescription( gameId: 'non-existent-id', - newDescription: 'New description', + description: 'New description', ); expect(updated, isFalse); @@ -280,7 +280,7 @@ void main() { await database.gameDao.updateGameColor( gameId: testGame1.id, - newColor: GameColor.green, + color: GameColor.green, ); final updatedGame = await database.gameDao.getGameById( @@ -292,7 +292,7 @@ void main() { test('updateGameColor() does nothing for non-existent game', () async { final updated = await database.gameDao.updateGameColor( gameId: 'non-existent-id', - newColor: GameColor.green, + color: GameColor.green, ); expect(updated, isFalse); @@ -306,7 +306,7 @@ void main() { final updated = await database.gameDao.updateGameIcon( gameId: testGame1.id, - newIcon: newIcon, + icon: newIcon, ); expect(updated, isTrue); @@ -319,7 +319,7 @@ void main() { test('updateGameIcon() does nothing for non-existent game', () async { final updated = await database.gameDao.updateGameIcon( gameId: 'non-existent-id', - newIcon: 'New icon', + icon: 'New icon', ); expect(updated, isFalse); @@ -333,19 +333,19 @@ void main() { const newName = 'New name'; await database.gameDao.updateGameName( gameId: testGame1.id, - newName: newName, + name: newName, ); const newGameColor = GameColor.teal; await database.gameDao.updateGameColor( gameId: testGame1.id, - newColor: newGameColor, + color: newGameColor, ); const newDescription = 'New description'; await database.gameDao.updateGameDescription( gameId: testGame1.id, - newDescription: newDescription, + description: newDescription, ); final updatedGame = await database.gameDao.getGameById( diff --git a/test/db_tests/entities/player_test.dart b/test/db_tests/entities/player_test.dart index 1e224bc..bfcced4 100644 --- a/test/db_tests/entities/player_test.dart +++ b/test/db_tests/entities/player_test.dart @@ -213,7 +213,7 @@ void main() { await database.playerDao.updatePlayerName( playerId: testPlayer1.id, - newName: newName, + name: newName, ); final player = await database.playerDao.getPlayerById( @@ -225,7 +225,7 @@ void main() { test('updatePlayerName() does nothing for non-existent player', () async { final updated = await database.playerDao.updatePlayerName( playerId: 'non-existent-id', - newName: 'New name', + name: 'New name', ); expect(updated, isFalse); @@ -240,7 +240,7 @@ void main() { final updated = await database.playerDao.updatePlayerDescription( playerId: testPlayer1.id, - newDescription: newDescription, + description: newDescription, ); expect(updated, isTrue); @@ -255,7 +255,7 @@ void main() { () async { final updated = await database.playerDao.updatePlayerDescription( playerId: 'non-existent-id', - newDescription: 'New description', + description: 'New description', ); expect(updated, isFalse); @@ -269,7 +269,7 @@ void main() { await database.playerDao.updatePlayerName( playerId: testPlayer1.id, - newName: 'First Update', + name: 'First Update', ); var fetchedPlayer = await database.playerDao.getPlayerById( @@ -279,7 +279,7 @@ void main() { await database.playerDao.updatePlayerName( playerId: testPlayer1.id, - newName: 'Second Update', + name: 'Second Update', ); fetchedPlayer = await database.playerDao.getPlayerById( @@ -289,7 +289,7 @@ void main() { await database.playerDao.updatePlayerDescription( playerId: testPlayer1.id, - newDescription: 'Third Update', + description: 'Third Update', ); fetchedPlayer = await database.playerDao.getPlayerById( diff --git a/test/db_tests/relationships/player_match_test.dart b/test/db_tests/relationships/player_match_test.dart index 85ccab9..6d879c3 100644 --- a/test/db_tests/relationships/player_match_test.dart +++ b/test/db_tests/relationships/player_match_test.dart @@ -253,7 +253,7 @@ void main() { await database.playerMatchDao.updateMatchPlayers( matchId: testMatch1.id, - newPlayer: newPlayers, + player: newPlayers, ); final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch( @@ -337,7 +337,7 @@ void main() { final newPlayersList = [testPlayer1, testPlayer2]; await database.playerMatchDao.updateMatchPlayers( matchId: testMatch1.id, - newPlayer: newPlayersList, + player: newPlayersList, ); matchPlayers = await database.matchDao.getMatchById( @@ -356,7 +356,7 @@ void main() { final updated = await database.playerMatchDao.updateMatchPlayers( matchId: testMatch1.id, - newPlayer: originalPlayers, + player: originalPlayers, ); expect(updated, isFalse); @@ -375,7 +375,7 @@ void main() { await database.matchDao.addMatch(match: testMatch1); final updated = await database.playerMatchDao.updateMatchPlayers( matchId: testMatch1.id, - newPlayer: [], + player: [], ); expect(updated, isFalse); diff --git a/test/db_tests/values/score_entry_test.dart b/test/db_tests/values/score_entry_test.dart index a4b5df4..f6cc292 100644 --- a/test/db_tests/values/score_entry_test.dart +++ b/test/db_tests/values/score_entry_test.dart @@ -435,7 +435,7 @@ void main() { final updated = await database.scoreEntryDao.updateScore( playerId: testPlayer1.id, matchId: testMatch1.id, - newEntry: newEntry, + entry: newEntry, ); expect(updated, isTrue); @@ -455,7 +455,7 @@ void main() { final updated = await database.scoreEntryDao.updateScore( playerId: testPlayer1.id, matchId: testMatch1.id, - newEntry: entryRound1, + entry: entryRound1, ); expect(updated, isFalse); -- 2.49.1