import 'package:clock/clock.dart'; import 'package:drift/drift.dart' hide isNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; void main() { late AppDatabase database; late Game testGame1; late Game testGame2; late Game testGame3; final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); final fakeClock = Clock(() => fixedDate); setUp(() { database = AppDatabase( DatabaseConnection( NativeDatabase.memory(), closeStreamsSynchronously: true, ), ); withClock(fakeClock, () { testGame1 = Game( name: 'Chess', ruleset: 'winner.single', description: 'A classic strategy game', color: 0xFF0000FF, icon: 'chess_icon', ); testGame2 = Game( id: 'game2', name: 'Poker', ruleset: 'Texas Hold\'em rules', description: 'winner.multiple', color: 0xFFFF0000, icon: 'poker_icon', ); testGame3 = Game( id: 'game3', name: 'Monopoly', description: 'A board game about real estate', ); }); }); tearDown(() async { await database.close(); }); group('Game Tests', () { // ==================== getAllGames ==================== test('getAllGames returns empty list when no games exist', () async { final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); }); test('Adding and fetching a single game works correctly', () async { await database.gameDao.addGame(game: testGame1); 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 { 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 names = allGames.map((g) => g.name).toList(); expect(names, containsAll(['Chess', 'Poker', 'Monopoly'])); }); // ==================== getGameById ==================== test('getGameById returns correct game', () async { await database.gameDao.addGame(game: testGame1); await database.gameDao.addGame(game: testGame2); 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('getGameById throws exception for non-existent game', () async { expect( () => database.gameDao.getGameById(gameId: 'non-existent-id'), throwsA(isA()), ); }); // ==================== addGame ==================== test('addGame returns true when game is added successfully', () async { final result = await database.gameDao.addGame(game: testGame1); expect(result, true); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 1); }); test('addGame returns false when game already exists', () async { final firstAdd = await database.gameDao.addGame(game: testGame1); expect(firstAdd, true); final secondAdd = await database.gameDao.addGame(game: testGame1); expect(secondAdd, false); final allGames = await database.gameDao.getAllGames(); expect(allGames.length, 1); }); test('addGame handles game with null optional fields', () async { final gameWithNulls = Game(name: 'Simple Game'); 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, isNull); expect(fetchedGame.color, isNull); expect(fetchedGame.icon, isNull); }); // ==================== 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); }); 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); }); 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); }); // ==================== deleteGame ==================== 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); }); test('deleteGame returns false for non-existent game', () async { final result = await database.gameDao.deleteGame( gameId: 'non-existent-id', ); expect(result, false); }); 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); }); // ==================== gameExists ==================== 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); }); test('gameExists returns false for non-existent game', () async { final exists = await database.gameDao.gameExists( gameId: 'non-existent-id', ); expect(exists, false); }); 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); }); // ==================== updateGameName ==================== 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); }); 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); }); // ==================== updateGameRuleset ==================== test('updateGameRuleset updates the ruleset correctly', () async { await database.gameDao.addGame(game: testGame1); await database.gameDao.updateGameRuleset( gameId: testGame1.id, newRuleset: 'New ruleset for chess', ); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, ); expect(updatedGame.ruleset, 'New ruleset for chess'); expect(updatedGame.name, testGame1.name); }); test('updateGameRuleset does nothing for non-existent game', () async { await database.gameDao.updateGameRuleset( gameId: 'non-existent-id', newRuleset: 'New Ruleset', ); final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); }); // ==================== updateGameDescription ==================== 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'); }); test('updateGameDescription can set description to null', () async { await database.gameDao.addGame(game: testGame1); await database.gameDao.updateGameDescription( gameId: testGame1.id, newDescription: null, ); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, ); expect(updatedGame.description, isNull); }); 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); }); // ==================== updateGameColor ==================== test('updateGameColor updates the color correctly', () async { await database.gameDao.addGame(game: testGame1); await database.gameDao.updateGameColor( gameId: testGame1.id, newColor: 0xFF00FF00, ); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, ); expect(updatedGame.color, 0xFF00FF00); }); test('updateGameColor can set color to null', () async { await database.gameDao.addGame(game: testGame1); await database.gameDao.updateGameColor( gameId: testGame1.id, newColor: null, ); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, ); expect(updatedGame.color, isNull); }); test('updateGameColor does nothing for non-existent game', () async { await database.gameDao.updateGameColor( gameId: 'non-existent-id', newColor: 0xFF00FF00, ); final allGames = await database.gameDao.getAllGames(); expect(allGames, isEmpty); }); // ==================== updateGameIcon ==================== 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'); }); test('updateGameIcon can set icon to null', () async { await database.gameDao.addGame(game: testGame1); await database.gameDao.updateGameIcon( gameId: testGame1.id, newIcon: null, ); final updatedGame = await database.gameDao.getGameById( gameId: testGame1.id, ); expect(updatedGame.icon, isNull); }); 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); }); // ==================== getGameCount ==================== test('getGameCount returns 0 when no games exist', () async { final count = await database.gameDao.getGameCount(); expect(count, 0); }); 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); }); 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); }); // ==================== deleteAllGames ==================== 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); }); test('deleteAllGames returns false when no games exist', () async { final result = await database.gameDao.deleteAllGames(); expect(result, false); }); // ==================== Edge Cases ==================== test('Game with special characters in name is stored correctly', () async { final specialGame = Game( name: 'Game\'s & "Special" ', description: 'Description with émojis 🎮🎲', ); 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 🎮🎲'); }); test('Game with empty string fields is stored correctly', () async { final emptyGame = Game( name: '', ruleset: '', description: '', icon: '', ); await database.gameDao.addGame(game: emptyGame); final fetchedGame = await database.gameDao.getGameById( gameId: emptyGame.id, ); expect(fetchedGame.name, ''); expect(fetchedGame.ruleset, ''); expect(fetchedGame.description, ''); expect(fetchedGame.icon, ''); }); test('Game with very long strings is stored correctly', () async { final longString = 'A' * 10000; final longGame = Game( name: longString, description: longString, ruleset: longString, ); 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?.length, 10000); }); 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: 0xFF123456, ); 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, 0xFF123456); expect(updatedGame.description, 'Updated Description'); expect(updatedGame.ruleset, testGame1.ruleset); expect(updatedGame.icon, testGame1.icon); }); }); }