diff --git a/lib/mod_list_troubleshooter.dart b/lib/mod_list_troubleshooter.dart index 3a0e676..34f059a 100644 --- a/lib/mod_list_troubleshooter.dart +++ b/lib/mod_list_troubleshooter.dart @@ -15,6 +15,7 @@ import 'package:rimworld_modman/mod_list.dart'; class ModListTroubleshooter { final ModList originalModList; ModList currentModList; + // These indices should ALWAYS represent the CURRENT selection of mods int _startIndex = 0; int _endIndex = 0; @@ -48,19 +49,24 @@ class ModListTroubleshooter { return currentModList; } + // If the current selection is not equal to our proposed step size + // We do not MOVE but instead just return the correct amount of mods from the start ModList linearForward({int stepSize = 20}) { - final totalMods = originalModList.activeMods.length; - - if (_startIndex >= totalMods || _startIndex == _endIndex) { - int newStart = totalMods - stepSize; - if (newStart < 0) { - newStart = 0; - } - _startIndex = newStart; + // If we are not "in step" + if (_endIndex - _startIndex == stepSize) { + // Move the indices forward by the step size, step forward + _startIndex += stepSize; + _endIndex += stepSize; + } else { + // Correct the end index to be in step + _endIndex = _startIndex + stepSize; + } + if (_endIndex > originalModList.activeMods.length) { + // If we are at the end of the list, move the start index such that we return + // At most the step size amount of mods + _endIndex = originalModList.activeMods.length; + _startIndex = (_endIndex - stepSize).clamp(0, _endIndex); } - - _startIndex = _startIndex.clamp(0, totalMods); - _endIndex = (_startIndex + stepSize).clamp(0, totalMods); final subset = originalModList.activeMods.keys.toList().sublist( _startIndex, @@ -69,24 +75,20 @@ class ModListTroubleshooter { currentModList.disableAll(); currentModList.enableMods(subset); - _startIndex = _endIndex; - return currentModList; } ModList linearBackward({int stepSize = 20}) { - final totalMods = originalModList.activeMods.length; - - if (_endIndex <= 0 || _startIndex == _endIndex) { - int newEnd = stepSize; - if (newEnd > totalMods) { - newEnd = totalMods; - } - _endIndex = newEnd; + if (_endIndex - _startIndex == stepSize) { + _startIndex -= stepSize; + _endIndex -= stepSize; + } else { + _startIndex = _endIndex - stepSize; + } + if (_startIndex < 0) { + _startIndex = 0; + _endIndex = stepSize.clamp(0, originalModList.activeMods.length); } - - _endIndex = _endIndex.clamp(0, totalMods); - _startIndex = (_endIndex - stepSize).clamp(0, _endIndex); final subset = originalModList.activeMods.keys.toList().sublist( _startIndex, @@ -95,8 +97,6 @@ class ModListTroubleshooter { currentModList.disableAll(); currentModList.enableMods(subset); - _endIndex = _startIndex; - return currentModList; } diff --git a/test/mod_list_troubleshooter_test.dart b/test/mod_list_troubleshooter_test.dart index 1e4ac91..fd5860d 100644 --- a/test/mod_list_troubleshooter_test.dart +++ b/test/mod_list_troubleshooter_test.dart @@ -75,7 +75,7 @@ void main() { }); test( - 'Bisect search should end up with half the mods every forward iteration until 1', + 'Should end up with half the mods every forward iteration until 1', () { final troubleshooter = ModListTroubleshooter(modList); @@ -103,7 +103,7 @@ void main() { }, ); test( - 'Bisect search should end up with half the mods every backward iteration until 1', + 'Should end up with half the mods every backward iteration until 1', () { final troubleshooter = ModListTroubleshooter(modList); @@ -131,7 +131,7 @@ void main() { }, ); test( - 'Bisect search should end up with half the mods every iteration until 1', + 'Should end up with half the mods every iteration until 1', () { final troubleshooter = ModListTroubleshooter(modList); @@ -158,7 +158,7 @@ void main() { expect(result.activeMods.keys.last, equals('test.mod9')); }, ); - test('Bisect should handle abuse gracefully', () { + test('Should handle abuse gracefully', () { final troubleshooter = ModListTroubleshooter(modList); var result = troubleshooter.binaryBackward(); @@ -225,7 +225,7 @@ void main() { }); test( - 'Linear search should end up with 10 mods every forward iteration', + 'Should end up with 10 mods every forward iteration', () { final troubleshooter = ModListTroubleshooter(modList); @@ -246,7 +246,7 @@ void main() { }, ); test( - 'Linear search should end up with 10 mods every backward iteration', + 'Should end up with 10 mods every backward iteration', () { final troubleshooter = ModListTroubleshooter(modList); @@ -266,7 +266,7 @@ void main() { expect(result.activeMods.keys.last, equals('test.mod9')); }, ); - test('Linear search should end up with 10 mods every iteration', () { + test('Should end up with 10 mods every iteration', () { final troubleshooter = ModListTroubleshooter(modList); var result = troubleshooter.linearBackward(stepSize: 10); @@ -281,10 +281,10 @@ void main() { result = troubleshooter.linearBackward(stepSize: 10); expect(result.activeMods.length, equals(10)); - expect(result.activeMods.keys.first, equals('test.mod20')); - expect(result.activeMods.keys.last, equals('test.mod29')); + expect(result.activeMods.keys.first, equals('test.mod10')); + expect(result.activeMods.keys.last, equals('test.mod19')); }); - test('Linear search should handle abuse gracefully', () { + test('Should handle abuse gracefully', () { final troubleshooter = ModListTroubleshooter(modList); var result = troubleshooter.linearBackward(stepSize: 10); @@ -302,7 +302,25 @@ void main() { result = troubleshooter.linearForward(stepSize: 10); expect(result.activeMods.length, equals(10)); }); - test('Linear search cannot return more items than there are', () { + test('Should handle different step sizes', () { + final troubleshooter = ModListTroubleshooter(modList); + + var result = troubleshooter.linearBackward(stepSize: 10); + expect(result.activeMods.length, equals(10)); + + result = troubleshooter.linearForward(stepSize: 10); + expect(result.activeMods.length, equals(10)); + + result = troubleshooter.linearBackward(stepSize: 10); + expect(result.activeMods.length, equals(10)); + + result = troubleshooter.linearForward(stepSize: 10); + expect(result.activeMods.length, equals(10)); + + result = troubleshooter.linearForward(stepSize: 10); + expect(result.activeMods.length, equals(10)); + }); + test('Cannot return more items than there are', () { final troubleshooter = ModListTroubleshooter(modList); var result = troubleshooter.linearBackward(stepSize: 10000); @@ -312,6 +330,63 @@ void main() { expect(result.activeMods.length, equals(30)); }); }); + group('Navigation tests', () { + late ModList modList = ModList(); + setUp(() { + modList = ModList(); + + // Add some base mods + for (int i = 0; i < 20; i++) { + final modId = 'test.mod$i'; + final mod = makeDummy().copyWith(name: 'Test Mod $i', id: modId); + modList.mods[modId] = mod; + } + + // Add some mods with dependencies + for (int i = 20; i < 30; i++) { + final modId = 'test.mod$i'; + final mod = makeDummy().copyWith( + name: 'Test Mod $i', + id: modId, + dependencies: ['test.mod${i - 20}'], // Depend on earlier mods + ); + modList.mods[modId] = mod; + } + modList.enableAll(); + }); + + test( + 'Mixed navigation should work', + () { + final troubleshooter = ModListTroubleshooter(modList); + + var result = troubleshooter.linearForward(stepSize: 10); + expect(result.activeMods.length, equals(10)); + expect(result.activeMods.keys.first, equals('test.mod0')); + expect(result.activeMods.keys.last, equals('test.mod9')); + + result = troubleshooter.binaryForward(); + expect(result.activeMods.length, equals(5)); + expect(result.activeMods.keys.first, equals('test.mod5')); + expect(result.activeMods.keys.last, equals('test.mod9')); + + result = troubleshooter.linearBackward(stepSize: 10); + expect(result.activeMods.length, equals(10)); + expect(result.activeMods.keys.first, equals('test.mod0')); + expect(result.activeMods.keys.last, equals('test.mod9')); + + result = troubleshooter.binaryForward(); + expect(result.activeMods.length, equals(5)); + expect(result.activeMods.keys.first, equals('test.mod5')); + expect(result.activeMods.keys.last, equals('test.mod9')); + + result = troubleshooter.linearForward(stepSize: 10); + expect(result.activeMods.length, equals(10)); + expect(result.activeMods.keys.first, equals('test.mod5')); + expect(result.activeMods.keys.last, equals('test.mod14')); + }, + ); + }); group('Loading dependencies', () { late ModList modList = ModList(); setUp(() { @@ -336,7 +411,7 @@ void main() { modList.enableAll(); }); // Not that it has any reason to since they're completely detached... - test('Loading dependencies should not fuck up troubleshooter', () { + test('Should not fuck up troubleshooter', () { final troubleshooter = ModListTroubleshooter(modList); final expectedFirst = [ 'test.mod1',