import 'package:rimworld_modman/mod.dart'; import 'package:rimworld_modman/mod_list.dart'; import 'package:test/test.dart'; const root = r'C:/Users/Administrator/Seafile/Games-RimWorld'; const modsRoot = '$root/294100'; const configRoot = '$root/AppData/RimWorld by Ludeon Studios/Config'; const configPath = '$configRoot/ModsConfig.xml'; const logsPath = '$root/ModManager'; Mod makeDummy() { return Mod( name: 'Dummy Mod', id: 'dummy', path: '', versions: ["1.5"], description: '', dependencies: [], loadAfter: [], loadBefore: [], incompatibilities: [], size: 0, isBaseGame: false, isExpansion: false, enabled: false, ); } void main() { group('Test sorting', () { test('Harmony should load before RimWorld', () { final list = ModList(); list.mods = { 'harmony': makeDummy().copyWith( name: 'Harmony', id: 'harmony', loadBefore: ['ludeon.rimworld'], ), 'ludeon.rimworld': makeDummy().copyWith( name: 'RimWorld', id: 'ludeon.rimworld', ), }; list.enableAll(); final order = list.generateLoadOrder(); final expected = ['harmony', 'ludeon.rimworld']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); test('Prepatcher should load after Harmony and RimWorld', () { final list = ModList(); list.mods = { 'prepatcher': makeDummy().copyWith( name: 'Prepatcher', id: 'prepatcher', dependencies: ['harmony'], loadAfter: ['ludeon.rimworld'], ), 'harmony': makeDummy().copyWith( name: 'Harmony', id: 'harmony', loadBefore: ['ludeon.rimworld'], ), 'ludeon.rimworld': makeDummy().copyWith( name: 'RimWorld', id: 'ludeon.rimworld', ), }; list.enableAll(); final order = list.generateLoadOrder(); final expected = ['harmony', 'ludeon.rimworld', 'prepatcher']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); test('RimWorld should load before Anomaly', () { final list = ModList(); list.mods = { 'ludeon.rimworld': makeDummy().copyWith( name: 'RimWorld', id: 'ludeon.rimworld', ), 'ludeon.rimworld.anomaly': makeDummy().copyWith( name: 'RimWorld Anomaly', id: 'ludeon.rimworld.anomaly', dependencies: ['ludeon.rimworld'], ), }; list.enableAll(); final order = list.generateLoadOrder(); final expected = ['ludeon.rimworld', 'ludeon.rimworld.anomaly']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); test('Disabled dummy mod should not be loaded', () { final list = ModList(); list.mods = { 'disabledDummy': makeDummy().copyWith( name: 'Disabled Dummy', id: 'disabledDummy', ), }; list.disableAll(); final order = list.generateLoadOrder(); final expected = []; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); test('Larger mods should load before smaller ones', () { final list = ModList(); list.mods = { 'smol': makeDummy().copyWith(name: 'Smol', id: 'smol', size: 100), 'yuuuge': makeDummy().copyWith( name: 'Yuuuge', id: 'yuuuge', size: 10000, ), }; list.enableAll(); final order = list.generateLoadOrder(); final expected = ['yuuuge', 'smol']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); test('Incompatible mods should return errors', () { final list = ModList(); list.mods = { 'incompatible': makeDummy().copyWith( name: 'Incompatible', id: 'incompatible', incompatibilities: ['harmony'], ), 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), }; list.enableAll(); final result = list.generateLoadOrder(); expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('incompatible')), isTrue); expect(result.errors.any((e) => e.contains('harmony')), isTrue); }); test('Base game should load before other mods', () { final list = ModList(); list.mods = { 'dummy': makeDummy().copyWith(size: 10000), 'ludeon.rimworld': makeDummy().copyWith( name: 'RimWorld', id: 'ludeon.rimworld', isBaseGame: true, ), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = ['ludeon.rimworld', 'dummy']; expect(result.errors, isEmpty); expect(result.loadOrder, equals(expected)); }); test('Base game and expansions should load before other mods', () { final list = ModList(); list.mods = { 'dummy': makeDummy().copyWith(size: 10000), 'ludeon.rimworld': makeDummy().copyWith( name: 'RimWorld', id: 'ludeon.rimworld', isBaseGame: true, ), 'ludeon.rimworld.anomaly': makeDummy().copyWith( name: 'RimWorld Anomaly', id: 'ludeon.rimworld.anomaly', dependencies: ['ludeon.rimworld'], isExpansion: true, ), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = ['ludeon.rimworld', 'ludeon.rimworld.anomaly', 'dummy']; expect(result.errors, isEmpty); expect(result.loadOrder, equals(expected)); }); test('Expansions should load in the correct order', () { final list = ModList(); // Intentionally left barren because that's how we get it out of the box // It is up to generateLoadOrder to fill in the details list.mods = { 'ludeon.rimworld': makeDummy().copyWith(id: 'ludeon.rimworld'), 'ludeon.rimworld.anomaly': makeDummy().copyWith( id: 'ludeon.rimworld.anomaly', ), 'ludeon.rimworld.ideology': makeDummy().copyWith( id: 'ludeon.rimworld.ideology', ), 'ludeon.rimworld.biotech': makeDummy().copyWith( id: 'ludeon.rimworld.biotech', ), 'ludeon.rimworld.royalty': makeDummy().copyWith( id: 'ludeon.rimworld.royalty', ), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = [ 'ludeon.rimworld', 'ludeon.rimworld.royalty', 'ludeon.rimworld.ideology', 'ludeon.rimworld.biotech', 'ludeon.rimworld.anomaly', ]; expect(result.errors, isEmpty); expect(result.loadOrder, equals(expected)); }); }); group('Test loadRequired', () { test('Dependencies should be automatically enabled', () { final list = ModList(); list.mods = { 'prepatcher': makeDummy().copyWith( name: 'Prepatcher', id: 'prepatcher', dependencies: ['harmony'], ), 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), }; list.disableAll(); list.setEnabled('prepatcher', true); final order = list.loadRequired(); final expected = ['harmony', 'prepatcher']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); test('Only required mods should be enabled', () { final list = ModList(); list.mods = { 'prepatcher': makeDummy().copyWith( name: 'Prepatcher', id: 'prepatcher', dependencies: ['harmony'], ), 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), 'dummy': makeDummy(), }; list.disableAll(); list.setEnabled('prepatcher', true); final order = list.loadRequired(); final expected = ['harmony', 'prepatcher']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); test('Incompatible mods should return errors', () { final list = ModList(); list.mods = { 'incompatible': makeDummy().copyWith( name: 'Incompatible', id: 'incompatible', incompatibilities: ['harmony'], ), 'prepatcher': makeDummy().copyWith( name: 'Prepatcher', id: 'prepatcher', dependencies: ['harmony'], ), 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), }; list.disableAll(); list.setEnabled('incompatible', true); list.setEnabled('prepatcher', true); final result = list.loadRequired(); // We say the mods are incompatible but load them anyway, who are we to decide what isn't loaded? final expected = ['harmony', 'prepatcher', 'incompatible']; expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('incompatible')), isTrue); expect(result.errors.any((e) => e.contains('harmony')), isTrue); expect(result.loadOrder, equals(expected)); }); test('Dependencies of dependencies should be loaded', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', dependencies: ['modB'], ), 'modB': makeDummy().copyWith( name: 'Mod B', id: 'modB', dependencies: ['modC'], ), 'modC': makeDummy().copyWith(name: 'Mod C', id: 'modC'), }; list.disableAll(); list.setEnabled('modA', true); final order = list.loadRequired(); final expected = ['modC', 'modB', 'modA']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); }); group('Test cyclic dependencies', () { test('Cyclic dependencies should return errors', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', dependencies: ['modB'], ), 'modB': makeDummy().copyWith( name: 'Mod B', id: 'modB', dependencies: ['modC'], ), 'modC': makeDummy().copyWith( name: 'Mod C', id: 'modC', dependencies: ['modA'], ), }; list.disableAll(); list.setEnabled('modA', true); final result = list.loadRequired(); // We try to not disable mods...... But cyclic dependencies are just hell // Can not handle it final expected = []; expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('modA')), isTrue); expect(result.errors.any((e) => e.contains('modB')), isTrue); expect(result.errors.any((e) => e.contains('modC')), isTrue); expect(result.loadOrder, equals(expected)); }); }); group('Test soft constraints', () { test('Load preferences should be respected when possible', () { final dummy = makeDummy(); final list = ModList(); list.mods = { 'modA': dummy.copyWith( name: 'Mod A', id: 'modA', loadAfter: ['modB'], loadBefore: ['modC'], ), 'modB': dummy.copyWith(name: 'Mod B', id: 'modB'), 'modC': dummy.copyWith(name: 'Mod C', id: 'modC'), }; list.enableAll(); final order = list.generateLoadOrder(); final expected = ['modB', 'modA', 'modC']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); }); //group('Test conflict detection', () { // test('All conflicts should be correctly identified', () { // final list = ModList(); // list.mods = { // 'modA': makeDummy().copyWith( // name: 'Mod A', // id: 'modA', // incompatibilities: ['modB', 'modC'], // ), // 'modB': makeDummy().copyWith(name: 'Mod B', id: 'modB'), // 'modC': makeDummy().copyWith(name: 'Mod C', id: 'modC'), // }; // list.enableAll(); // final conflicts = list.checkIncompatibilities( // list.activeMods.keys.toList(), // ); // expect(conflicts.length, equals(2)); // // Check if conflicts contain these pairs (order doesn't matter) // expect( // conflicts.any( // (c) => // (c[0] == 'modA' && c[1] == 'modB') || // (c[0] == 'modB' && c[1] == 'modA'), // ), // isTrue, // ); // expect( // conflicts.any( // (c) => // (c[0] == 'modA' && c[1] == 'modC') || // (c[0] == 'modC' && c[1] == 'modA'), // ), // isTrue, // ); // }); //}); group('Test enable/disable functionality', () { test('Enable and disable methods should work correctly', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith(name: 'Mod A', id: 'modA'), 'modB': makeDummy().copyWith(name: 'Mod B', id: 'modB'), }; list.enableAll(); for (final mod in list.mods.values) { expect(mod.enabled, isTrue); } list.disableAll(); for (final mod in list.mods.values) { expect(mod.enabled, isFalse); } }); }); group('Test base game and expansion handling', () { test('Base game and expansions should be correctly ordered', () { final list = ModList(); list.mods = { 'ludeon.rimworld': makeDummy().copyWith( name: 'RimWorld', id: 'ludeon.rimworld', isBaseGame: true, ), 'ludeon.rimworld.anomaly': makeDummy().copyWith( name: 'RimWorld Anomaly', id: 'ludeon.rimworld.anomaly', dependencies: ['ludeon.rimworld'], isExpansion: true, ), }; list.enableAll(); final order = list.generateLoadOrder(); // Base game should load before any expansions final baseGameIndex = order.loadOrder.indexOf('ludeon.rimworld'); final expansionIndex = order.loadOrder.indexOf('ludeon.rimworld.anomaly'); expect(order.errors, isEmpty); expect(baseGameIndex, lessThan(expansionIndex)); }); }); group('Test complex dependency chains', () { test('Complex dependency chains should resolve correctly', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', dependencies: ['modB'], ), 'modB': makeDummy().copyWith( name: 'Mod B', id: 'modB', dependencies: ['modC'], ), 'modC': makeDummy().copyWith( name: 'Mod C', id: 'modC', dependencies: ['modD'], ), 'modD': makeDummy().copyWith(name: 'Mod D', id: 'modD'), }; list.disableAll(); list.setEnabled('modA', true); final result = list.loadRequired(); final expected = ['modD', 'modC', 'modB', 'modA']; expect(result.errors, isEmpty); expect(result.loadOrder, equals(expected)); }); }); group('Test missing dependencies', () { test('Should detect missing dependencies and return errors', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', dependencies: ['modB', 'nonExistentMod'], ), 'modB': makeDummy().copyWith(name: 'Mod B', id: 'modB'), }; list.enableAll(); // This should throw an exception because the dependency doesn't exist final result = list.generateLoadOrder(); expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('nonExistentMod')), isTrue); }); test('Should handle multiple missing dependencies correctly', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', dependencies: ['missing1'], ), 'modB': makeDummy().copyWith( name: 'Mod B', id: 'modB', dependencies: ['missing2'], ), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = ['modB', 'modA']; expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('missing1')), isTrue); expect(result.errors.any((e) => e.contains('missing2')), isTrue); expect(result.loadOrder, equals(expected)); }); }); group('Test missing loadBefore/loadAfter relationships', () { test('Should handle missing loadBefore relationships', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', loadBefore: ['nonExistentMod'], ), }; list.enableAll(); // Should not throw exception for soft constraints // But might generate a warning that we could check for final order = list.generateLoadOrder(); final expected = ['modA']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); test('Should handle missing loadAfter relationships', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', loadAfter: ['nonExistentMod'], ), }; list.enableAll(); // Should not throw exception for soft constraints final order = list.generateLoadOrder(); final expected = ['modA']; expect(order.errors, isEmpty); expect(order.loadOrder, equals(expected)); }); }); group('Test BuildLoadOrder error handling', () { test('Should return errors for incompatibilities', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', incompatibilities: ['modB'], ), 'modB': makeDummy().copyWith(name: 'Mod B', id: 'modB'), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = ['modB', 'modA']; expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('incompatible')), isTrue); expect(result.errors.any((e) => e.contains('modA')), isTrue); expect(result.errors.any((e) => e.contains('modB')), isTrue); expect(result.loadOrder, equals(expected)); }); test('Should handle a combination of errors simultaneously', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', dependencies: ['missingDep'], incompatibilities: ['modB'], ), 'modB': makeDummy().copyWith(name: 'Mod B', id: 'modB'), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = ['modB', 'modA']; expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('missingDep')), isTrue); expect(result.errors.any((e) => e.contains('incompatible')), isTrue); expect(result.loadOrder, equals(expected)); }); }); group('Test dependency resolution with constraints', () { test( 'Should resolve dependencies while respecting load order constraints', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', dependencies: ['modB'], loadAfter: ['modC'], ), 'modB': makeDummy().copyWith(name: 'Mod B', id: 'modB'), 'modC': makeDummy().copyWith(name: 'Mod C', id: 'modC'), }; list.enableAll(); final order = list.generateLoadOrder(); expect(order.errors, isEmpty); // modB should load before modA due to dependency expect( order.loadOrder.indexOf('modB'), lessThan(order.loadOrder.indexOf('modA')), ); // modC should load before modA due to loadAfter constraint expect( order.loadOrder.indexOf('modC'), lessThan(order.loadOrder.indexOf('modA')), ); }, ); test('Should detect and report conflicting constraints', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', loadBefore: ['modB'], ), 'modB': makeDummy().copyWith( name: 'Mod B', id: 'modB', loadBefore: ['modA'], ), }; list.enableAll(); // These constraints create a circular dependency which should cause an error try { list.generateLoadOrder(); fail('Expected an exception to be thrown due to circular constraints'); } catch (e) { // Verify error is about circular dependencies or conflicting constraints expect( e.toString().toLowerCase().contains('conflict') || e.toString().toLowerCase().contains('circular'), isTrue, ); } }); }); group('Test BuildLoadOrder with result object', () { test('Should return successful load order with no errors', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith(name: 'Mod A', id: 'modA'), 'modB': makeDummy().copyWith(name: 'Mod B', id: 'modB'), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = ['modB', 'modA']; expect(result.errors, isEmpty); expect(result.loadOrder, equals(expected)); }); test('Should return errors for missing dependencies', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( name: 'Mod A', id: 'modA', dependencies: ['nonExistentMod'], ), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = ['modA']; expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('nonExistentMod')), isTrue); expect(result.loadOrder, equals(expected)); }); test('Should return both valid load order and errors', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith(name: 'Mod A', id: 'modA'), 'modB': makeDummy().copyWith( name: 'Mod B', id: 'modB', dependencies: ['nonExistentMod'], ), }; list.enableAll(); final result = list.generateLoadOrder(); final expected = ['modB', 'modA']; expect(result.errors, isNotEmpty); expect(result.errors.any((e) => e.contains('nonExistentMod')), isTrue); expect(result.loadOrder, equals(expected)); }); }); 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(); final expected = ['modA', 'modB']; // 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('modA')), isTrue); expect(result.errors.any((e) => e.contains('modB')), isTrue); // But mods should still be loaded anyway (the "It's fucked but anyway" philosophy) expect(result.loadOrder, equals(expected)); }, ); }); 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(); final expected = ['modA']; // Should still generate a valid load order despite missing soft constraints expect(result.loadOrder, equals(expected)); // 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(); final expected = ['existingMod', 'modA']; // Should still generatdeequals(mopected) expect(result.loadOrder.contains('existingMod'), isTrue); // The existing loadAfter relationship should be honored expect(result.loadOrder, equals(expected)); // 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 expected = ['modA']; 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, ); expect(result.loadOrder, equals(expected)); }, ); 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(); final expected = ['modC', 'modB', 'modA']; // 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('modB')), isTrue); expect(result.errors.any((e) => e.contains('modC')), isTrue); // But all mods should still be loaded in the best possible order expect(result.loadOrder, equals(expected)); }, ); 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')), ); }, ); }); }