From 0a6032d77bb2f97574b6b5d3cccf6721242e508b Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sun, 16 Mar 2025 13:53:15 +0100 Subject: [PATCH] Refactor test completely --- test/mod_list_test.dart | 425 +++++++++++++++++++++++----------------- 1 file changed, 247 insertions(+), 178 deletions(-) diff --git a/test/mod_list_test.dart b/test/mod_list_test.dart index bf03379..1aebc9a 100644 --- a/test/mod_list_test.dart +++ b/test/mod_list_test.dart @@ -8,8 +8,8 @@ const configRoot = '$root/AppData/RimWorld by Ludeon Studios/Config'; const configPath = '$configRoot/ModsConfig.xml'; const logsPath = '$root/ModManager'; -Map generateDummyMods() { - final dummyMod = Mod( +Mod makeDummy() { + return Mod( name: 'Dummy Mod', id: 'dummy', path: '', @@ -24,170 +24,243 @@ Map generateDummyMods() { isExpansion: false, enabled: false, ); - - final dummyMods = { - 'harmony': dummyMod.copyWith( - name: 'Harmony', - id: 'harmony', - loadBefore: ["ludeon.rimworld"], - size: 47, - ), - 'prepatcher': dummyMod.copyWith( - name: 'Prepatcher', - id: 'prepatcher', - loadAfter: ["ludeon.rimworld"], - dependencies: ["harmony"], - size: 47, - ), - 'ludeon.rimworld': dummyMod.copyWith( - name: 'RimWorld', - id: 'ludeon.rimworld', - isBaseGame: true, - ), - 'ludeon.rimworld.anomaly': dummyMod.copyWith( - name: 'RimWorld Anomaly', - id: 'ludeon.rimworld.anomaly', - isExpansion: true, - ), - 'disabledDummy': dummyMod.copyWith( - name: 'Disabled Dummy', - id: 'disabledDummy', - ), - 'yuuuge': dummyMod.copyWith(name: 'Yuuuge', id: 'yuuuge', size: 1000000), - 'smol': dummyMod.copyWith(name: 'Smol', id: 'smol', size: 1), - 'incompatible': dummyMod.copyWith( - name: 'Incompatible', - id: 'incompatible', - size: 1, - incompatibilities: ['harmony'], - ), - }; - - return dummyMods; } void main() { - final dummyMods = generateDummyMods(); - final dummyList = ModList(); - dummyList.mods = dummyMods; - dummyList.enableAll(); - dummyList.setEnabled('disabledDummy', false); - dummyList.setEnabled('incompatible', false); - final sortedMods = dummyList.generateLoadOrder(); - group('Test sorting', () { test('Harmony should load before RimWorld', () { - final harmonyIndex = sortedMods.indexOf('harmony'); - final rimworldIndex = sortedMods.indexOf('ludeon.rimworld'); + final list = ModList(); + list.mods = { + 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), + 'ludeon.rimworld': makeDummy().copyWith( + name: 'RimWorld', + id: 'ludeon.rimworld', + ), + }; + list.enableAll(); + final order = list.generateLoadOrder(); + + final harmonyIndex = order.indexOf('harmony'); + final rimworldIndex = order.indexOf('ludeon.rimworld'); expect(harmonyIndex, lessThan(rimworldIndex)); }); test('Prepatcher should load after Harmony and RimWorld', () { - final prepatcherIndex = sortedMods.indexOf('prepatcher'); - final harmonyIndex = sortedMods.indexOf('harmony'); - final rimworldIndex = sortedMods.indexOf('ludeon.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 prepatcherIndex = order.indexOf('prepatcher'); + final harmonyIndex = order.indexOf('harmony'); + final rimworldIndex = order.indexOf('ludeon.rimworld'); expect(prepatcherIndex, greaterThan(harmonyIndex)); expect(prepatcherIndex, greaterThan(rimworldIndex)); }); test('RimWorld should load before Anomaly', () { - final rimworldIndex = sortedMods.indexOf('ludeon.rimworld'); - final anomalyIndex = sortedMods.indexOf('ludeon.rimworld.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', + ), + }; + list.enableAll(); + final order = list.generateLoadOrder(); + + final rimworldIndex = order.indexOf('ludeon.rimworld'); + final anomalyIndex = order.indexOf('ludeon.rimworld.anomaly'); expect(rimworldIndex, lessThan(anomalyIndex)); }); test('Disabled dummy mod should not be loaded', () { - final disabledIndex = sortedMods.indexOf('disabledDummy'); + final list = ModList(); + list.mods = { + 'disabledDummy': makeDummy().copyWith( + name: 'Disabled Dummy', + id: 'disabledDummy', + ), + }; + list.disableAll(); + final order = list.generateLoadOrder(); + + final disabledIndex = order.indexOf('disabledDummy'); expect(disabledIndex, isNegative); }); test('Larger mods should load before smaller ones', () { - final smolIndex = sortedMods.indexOf('smol'); - final yuuugeIndex = sortedMods.indexOf('yuuuge'); + 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 smolIndex = order.indexOf('smol'); + final yuuugeIndex = order.indexOf('yuuuge'); expect(yuuugeIndex, lessThan(smolIndex)); }); test('Incompatible mods should throw exception', () { - dummyList.setEnabled('incompatible', true); - expect(() => dummyList.generateLoadOrder(), throwsException); + final list = ModList(); + list.mods = { + 'incompatible': makeDummy().copyWith( + name: 'Incompatible', + id: 'incompatible', + incompatibilities: ['harmony'], + ), + 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), + }; + list.enableAll(); + expect(() => list.generateLoadOrder(), throwsException); }); }); - final dummyMods2 = generateDummyMods(); - final dummyList2 = ModList(); - dummyList2.mods = dummyMods2; - dummyList2.disableAll(); - dummyList2.setEnabled('prepatcher', true); - final sortedMods2 = dummyList2.loadRequired(); - group('Test loadRequired', () { test('Dependencies should be automatically enabled', () { - final harmonyIndex = sortedMods2.indexOf('harmony'); - expect(harmonyIndex, isNot(-1)); + 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(); + expect(order.indexOf('harmony'), isNot(-1)); }); test('Only required mods should be enabled', () { - for (final mod in dummyMods2.keys) { - if (mod != 'prepatcher' && mod != 'harmony') { - expect(sortedMods2.indexOf(mod), isNegative); - } - } + 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(); + expect(order.indexOf('harmony'), isNot(-1)); + expect(order.indexOf('dummy'), -1); }); test('Incompatible mods should throw exception', () { - dummyList2.setEnabled('incompatible', true); - expect(() => dummyList2.loadRequired(), throwsException); + 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); + expect(() => list.loadRequired(), throwsException); + }); + 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(); + expect(order.indexOf('modA'), isNot(-1)); + expect(order.indexOf('modB'), isNot(-1)); + expect(order.indexOf('modC'), isNot(-1)); }); }); group('Test cyclic dependencies', () { test('Cyclic dependencies should throw exception', () { - final cyclicMods = generateDummyMods(); - // Create a cyclic dependency: A -> B -> C -> A - cyclicMods['modA'] = cyclicMods['smol']!.copyWith( - name: 'Mod A', - id: 'modA', - dependencies: ['modB'], - ); - cyclicMods['modB'] = cyclicMods['smol']!.copyWith( - name: 'Mod B', - id: 'modB', - dependencies: ['modC'], - ); - cyclicMods['modC'] = cyclicMods['smol']!.copyWith( - name: 'Mod C', - id: 'modC', - dependencies: ['modA'], - ); - final list = ModList(); - list.mods = cyclicMods; - list.enableAll(); - - expect(() => list.generateLoadOrder(), throwsException); + 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); + expect(() => list.loadRequired(), throwsException); }); }); group('Test soft constraints', () { test('Load preferences should be respected when possible', () { - final softConstraintMods = generateDummyMods(); - softConstraintMods['modA'] = softConstraintMods['smol']!.copyWith( - name: 'Mod A', - id: 'modA', - loadAfter: ['modB'], - loadBefore: ['modC'], - ); - softConstraintMods['modB'] = softConstraintMods['smol']!.copyWith( - name: 'Mod B', - id: 'modB', - ); - softConstraintMods['modC'] = softConstraintMods['smol']!.copyWith( - name: 'Mod C', - id: 'modC', - ); - + final dummy = makeDummy(); final list = ModList(); - list.mods = softConstraintMods; + 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(); @@ -202,25 +275,17 @@ void main() { group('Test conflict detection', () { test('All conflicts should be correctly identified', () { - final incompatibleMods = generateDummyMods(); - incompatibleMods['modA'] = incompatibleMods['smol']!.copyWith( - name: 'Mod A', - id: 'modA', - incompatibilities: ['modB', 'modC'], - ); - incompatibleMods['modB'] = incompatibleMods['smol']!.copyWith( - name: 'Mod B', - id: 'modB', - ); - incompatibleMods['modC'] = incompatibleMods['smol']!.copyWith( - name: 'Mod C', - id: 'modC', - ); - final list = ModList(); - list.mods = incompatibleMods; + 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(); expect(conflicts.length, equals(2)); @@ -246,9 +311,11 @@ void main() { group('Test enable/disable functionality', () { test('Enable and disable methods should work correctly', () { - final testMods = generateDummyMods(); final list = ModList(); - list.mods = testMods; + 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) { @@ -264,11 +331,23 @@ void main() { group('Test base game and expansion handling', () { test('Base game and expansions should be correctly ordered', () { - final gameMods = generateDummyMods(); final list = ModList(); - list.mods = gameMods; - list.enableAll(); - list.setEnabled('incompatible', false); // Avoid exception + list.mods = { + 'ludeon.rimworld': makeDummy().copyWith( + name: 'RimWorld', + id: 'ludeon.rimworld', + ), + 'ludeon.rimworld.anomaly': makeDummy().copyWith( + name: 'RimWorld Anomaly', + id: 'ludeon.rimworld.anomaly', + ), + 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), + 'prepatcher': makeDummy().copyWith( + name: 'Prepatcher', + id: 'prepatcher', + dependencies: ['harmony'], + ), + }; final order = list.generateLoadOrder(); @@ -290,30 +369,25 @@ void main() { group('Test complex dependency chains', () { test('Complex dependency chains should resolve correctly', () { - final complexMods = generateDummyMods(); - // Create a chain: A -> B -> C -> D - complexMods['modA'] = complexMods['smol']!.copyWith( - name: 'Mod A', - id: 'modA', - dependencies: ['modB'], - ); - complexMods['modB'] = complexMods['smol']!.copyWith( - name: 'Mod B', - id: 'modB', - dependencies: ['modC'], - ); - complexMods['modC'] = complexMods['smol']!.copyWith( - name: 'Mod C', - id: 'modC', - dependencies: ['modD'], - ); - complexMods['modD'] = complexMods['smol']!.copyWith( - name: 'Mod D', - id: 'modD', - ); - final list = ModList(); - list.mods = complexMods; + 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); @@ -334,23 +408,18 @@ void main() { group('Test constraint prioritization', () { test('Hard dependencies should override soft constraints', () { - final constraintMods = generateDummyMods(); - // A depends on B but wants to load before it (impossible) - constraintMods['modA'] = constraintMods['smol']!.copyWith( - name: 'Mod A', - id: 'modA', - dependencies: ['modB'], - loadBefore: ['modB'], - ); - constraintMods['modB'] = constraintMods['smol']!.copyWith( - name: 'Mod B', - id: 'modB', - ); - final list = ModList(); - list.mods = constraintMods; - list.enableAll(); + list.mods = { + 'modA': makeDummy().copyWith( + name: 'Mod A', + id: 'modA', + dependencies: ['modB'], + loadBefore: ['modB'], + ), + 'modB': makeDummy().copyWith(name: 'Mod B', id: 'modB'), + }; + list.enableAll(); final order = list.generateLoadOrder(); // Hard dependency must win