From 07264d1f752be4b48a5753e05dcb68fe058d6782 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Mon, 17 Mar 2025 21:05:27 +0100 Subject: [PATCH] Add a few more tests --- test/mod_list_test.dart | 234 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 227 insertions(+), 7 deletions(-) diff --git a/test/mod_list_test.dart b/test/mod_list_test.dart index e4915f0..432b7f4 100644 --- a/test/mod_list_test.dart +++ b/test/mod_list_test.dart @@ -8,13 +8,6 @@ const configRoot = '$root/AppData/RimWorld by Ludeon Studios/Config'; const configPath = '$configRoot/ModsConfig.xml'; const logsPath = '$root/ModManager'; -// Few things we missed: -// 1. BuildLoadOrder should either return or throw a List in addition to... List -// That will represent errors -// Those would be incompatibilities, missing dependencies, unable to setup before/after and so on -// 2. Write tests for missing dependencies -// 3. Maybe write tests for missing loadBefore/loadAfter - Mod makeDummy() { return Mod( name: 'Dummy Mod', @@ -673,4 +666,231 @@ void main() { // modB might be excluded from load order due to its error }); }); + + group('Debug missing dependencies', () { + test( + 'Should provide detailed information about missing dependencies but still load mods', + () { + final list = ModList(); + list.mods = { + 'modA': makeDummy().copyWith( + name: 'Mod A', + id: 'modA', + dependencies: ['missingDep1', 'missingDep2'], + ), + 'modB': makeDummy().copyWith( + name: 'Mod B', + id: 'modB', + dependencies: ['modA', 'missingDep3'], + ), + }; + list.enableAll(); + + final result = list.generateLoadOrder(); + + // Verify all missing dependencies are reported + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('missingDep1')), isTrue); + expect(result.errors.any((e) => e.contains('missingDep2')), isTrue); + expect(result.errors.any((e) => e.contains('missingDep3')), isTrue); + + // Verify errors include the mod that requires the missing dependency + expect(result.errors.any((e) => e.contains('Mod A')), isTrue); + expect(result.errors.any((e) => e.contains('Mod B')), isTrue); + + // But mods should still be loaded anyway (the "It's fucked but anyway" philosophy) + expect(result.loadOrder.contains('modA'), isTrue); + expect(result.loadOrder.contains('modB'), isTrue); + }, + ); + }); + + group('Debug missing loadBefore/loadAfter relationships', () { + test('Should handle and report missing loadBefore targets', () { + final list = ModList(); + list.mods = { + 'modA': makeDummy().copyWith( + name: 'Mod A', + id: 'modA', + loadBefore: ['missingMod1', 'missingMod2'], + ), + }; + list.enableAll(); + + final result = list.generateLoadOrder(); + + // Should still generate a valid load order despite missing soft constraints + expect(result.loadOrder.contains('modA'), isTrue); + + // System should track or report the missing loadBefore targets + // This may be implementation-specific - modify if needed based on how your system handles this + // May need to implement a warnings list in the BuildLoadOrderResult + expect(result.errors, isEmpty); // Soft constraints shouldn't cause errors + }); + + test('Should handle and report missing loadAfter targets', () { + final list = ModList(); + list.mods = { + 'modA': makeDummy().copyWith( + name: 'Mod A', + id: 'modA', + loadAfter: ['missingMod1', 'existingMod'], + ), + 'existingMod': makeDummy().copyWith( + name: 'Existing Mod', + id: 'existingMod', + ), + }; + list.enableAll(); + + final result = list.generateLoadOrder(); + + // Should still generate a valid load order despite missing soft constraints + expect(result.loadOrder.contains('modA'), isTrue); + expect(result.loadOrder.contains('existingMod'), isTrue); + + // The existing loadAfter relationship should be honored + expect( + result.loadOrder.indexOf('existingMod'), + lessThan(result.loadOrder.indexOf('modA')), + ); + + // System should track or report the missing loadAfter targets + expect(result.errors, isEmpty); // Soft constraints shouldn't cause errors + }); + }); + + group('Debug multiple constraint issues simultaneously', () { + test( + 'Should detect and report both missing dependencies and loadBefore/loadAfter issues but still load mods', + () { + final list = ModList(); + list.mods = { + 'modA': makeDummy().copyWith( + name: 'Mod A', + id: 'modA', + dependencies: ['missingDep'], + loadBefore: ['missingMod'], + loadAfter: ['anotherMissingMod'], + ), + }; + list.enableAll(); + + final result = list.generateLoadOrder(); + + // Should report the missing dependency + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('missingDep')), isTrue); + + // Missing soft constraints shouldn't cause errors but should be handled gracefully + expect(result.errors.any((e) => e.contains('missingMod')), isFalse); + expect( + result.errors.any((e) => e.contains('anotherMissingMod')), + isFalse, + ); + + // Mod should still be loaded despite missing dependencies + expect(result.loadOrder.contains('modA'), isTrue); + }, + ); + + test( + 'Should provide clear debugging information for complex dependency chains with issues while loading all possible mods', + () { + final list = ModList(); + list.mods = { + 'modA': makeDummy().copyWith( + name: 'Mod A', + id: 'modA', + dependencies: ['modB', 'modC'], + ), + 'modB': makeDummy().copyWith( + name: 'Mod B', + id: 'modB', + dependencies: ['missingDep1'], + ), + 'modC': makeDummy().copyWith( + name: 'Mod C', + id: 'modC', + dependencies: ['missingDep2'], + loadAfter: ['nonExistentMod'], + ), + }; + list.enableAll(); + + final result = list.generateLoadOrder(); + + // Should report all missing dependencies + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('missingDep1')), isTrue); + expect(result.errors.any((e) => e.contains('missingDep2')), isTrue); + + // Should indicate which mods are affected by these missing dependencies + expect(result.errors.any((e) => e.contains('Mod B')), isTrue); + expect(result.errors.any((e) => e.contains('Mod C')), isTrue); + + // But all mods should still be loaded in the best possible order + expect(result.loadOrder.contains('modA'), isTrue); + expect(result.loadOrder.contains('modB'), isTrue); + expect(result.loadOrder.contains('modC'), isTrue); + + // And existing constraints should be respected + // modA depends on modB and modC, so it should load after them + expect( + result.loadOrder.indexOf('modB'), + lessThan(result.loadOrder.indexOf('modA')), + ); + expect( + result.loadOrder.indexOf('modC'), + lessThan(result.loadOrder.indexOf('modA')), + ); + }, + ); + + test( + 'Should try to satisfy as many dependencies as possible in "it\'s fucked but anyway" mode', + () { + final list = ModList(); + list.mods = { + 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), + 'missingFramework': makeDummy().copyWith( + name: 'Missing Framework', + id: 'missingFramework', + dependencies: ['nonExistentDep'], + ), + 'modA': makeDummy().copyWith( + name: 'Mod A', + id: 'modA', + dependencies: ['harmony', 'missingFramework', 'anotherMissingDep'], + ), + }; + list.enableAll(); + + final result = list.generateLoadOrder(); + + // Should report missing dependencies + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('nonExistentDep')), isTrue); + expect( + result.errors.any((e) => e.contains('anotherMissingDep')), + isTrue, + ); + + // All mods should still be included in load order despite missing dependencies + expect(result.loadOrder.contains('harmony'), isTrue); + expect(result.loadOrder.contains('missingFramework'), isTrue); + expect(result.loadOrder.contains('modA'), isTrue); + + // Existing dependencies should still be respected in the ordering + expect( + result.loadOrder.indexOf('harmony'), + lessThan(result.loadOrder.indexOf('modA')), + ); + expect( + result.loadOrder.indexOf('missingFramework'), + lessThan(result.loadOrder.indexOf('modA')), + ); + }, + ); + }); }