diff --git a/lib/mod_list.dart b/lib/mod_list.dart index 980517f..d3cd200 100644 --- a/lib/mod_list.dart +++ b/lib/mod_list.dart @@ -4,6 +4,15 @@ import 'package:rimworld_modman/logger.dart'; import 'package:rimworld_modman/mod.dart'; import 'package:xml/xml.dart'; +class LoadOrder { + final List loadOrder = []; + final List errors = []; + + LoadOrder(); + + bool get hasErrors => errors.isNotEmpty; +} + class ModList { String configPath = ''; String modsPath = ''; @@ -208,9 +217,8 @@ class ModList { } } - List> checkIncompatibilities() { + List> checkIncompatibilities(List activeModIds) { List> conflicts = []; - List activeModIds = activeMods.keys.toList(); // Only check each pair once for (final modId in activeModIds) { @@ -227,13 +235,16 @@ class ModList { } /// Generate a load order for active mods - List generateLoadOrder() { + LoadOrder generateLoadOrder([LoadOrder? loadOrder]) { + loadOrder ??= LoadOrder(); // Check for incompatibilities first - final conflicts = checkIncompatibilities(); + final conflicts = checkIncompatibilities(activeMods.keys.toList()); if (conflicts.isNotEmpty) { - throw Exception( - "Incompatible mods selected: ${conflicts.map((c) => "${c[0]} and ${c[1]}").join(', ')}", - ); + for (final conflict in conflicts) { + loadOrder.errors.add( + "Incompatible mods selected: ${conflict[0]} and ${conflict[1]}", + ); + } } // Reset all marks for topological sort @@ -243,11 +254,10 @@ class ModList { mod.position = -1; } - final result = []; int position = 0; // Topological sort - void visit(Mod mod) { + void visit(Mod mod, LoadOrder loadOrder) { if (!mod.enabled) { mod.visited = true; return; @@ -255,7 +265,7 @@ class ModList { if (mod.mark) { final cyclePath = mods.values.where((m) => m.mark).map((m) => m.name).toList(); - throw Exception( + loadOrder.errors.add( "Cyclic dependency detected: ${cyclePath.join(' -> ')}", ); } @@ -266,26 +276,26 @@ class ModList { // Visit all dependencies for (String depId in mod.dependencies) { if (activeMods.containsKey(depId)) { - visit(mods[depId]!); + visit(mods[depId]!, loadOrder); } } mod.mark = false; mod.visited = true; mod.position = position++; - result.add(mod.id); + loadOrder.loadOrder.add(mod.id); } } // Visit all nodes for (final mod in mods.values) { if (!mod.visited) { - visit(mod); + visit(mod, loadOrder); } } // Optimize for soft constraints - return _optimizeSoftConstraints(result); + return _optimizeSoftConstraints(loadOrder: loadOrder); } /// Calculate how many soft constraints are satisfied @@ -326,18 +336,20 @@ class ModList { } /// Optimize for soft constraints using a greedy approach - List _optimizeSoftConstraints( - List initialOrder, { + LoadOrder _optimizeSoftConstraints({ int maxIterations = 5, + LoadOrder? loadOrder, }) { - List bestOrder = List.from(initialOrder); - Map scoreInfo = _calculateSoftConstraintsScore(bestOrder); + loadOrder ??= LoadOrder(); + Map scoreInfo = _calculateSoftConstraintsScore( + loadOrder.loadOrder, + ); int bestScore = scoreInfo['satisfied']!; int total = scoreInfo['total']!; if (total == 0 || bestScore == total) { // All constraints satisfied or no constraints, sort by size where possible - return _sortSizeWithinConstraints(bestOrder); + return _sortSizeWithinConstraints(loadOrder: loadOrder); } // Use a limited number of improvement passes @@ -345,18 +357,18 @@ class ModList { bool improved = false; // Try moving each mod to improve score - for (int i = 0; i < bestOrder.length; i++) { - String modId = bestOrder[i]; + for (int i = 0; i < loadOrder.loadOrder.length; i++) { + String modId = loadOrder.loadOrder[i]; Mod mod = mods[modId]!; // Calculate current local score for this mod Map currentPositions = {}; - for (int idx = 0; idx < bestOrder.length; idx++) { - currentPositions[bestOrder[idx]] = idx; + for (int idx = 0; idx < loadOrder.loadOrder.length; idx++) { + currentPositions[loadOrder.loadOrder[idx]] = idx; } // Try moving this mod to different positions - for (int newPos = 0; newPos < bestOrder.length; newPos++) { + for (int newPos = 0; newPos < loadOrder.loadOrder.length; newPos++) { if (newPos == i) continue; // Skip if move would break hard dependencies @@ -365,7 +377,7 @@ class ModList { // Moving earlier // Check if any mod between newPos and i depends on this mod for (int j = newPos; j < i; j++) { - String depModId = bestOrder[j]; + String depModId = loadOrder.loadOrder[j]; if (mods[depModId]!.dependencies.contains(modId)) { skip = true; break; @@ -375,7 +387,7 @@ class ModList { // Moving later // Check if this mod depends on any mod between i and newPos for (int j = i + 1; j <= newPos; j++) { - String depModId = bestOrder[j]; + String depModId = loadOrder.loadOrder[j]; if (mod.dependencies.contains(depModId)) { skip = true; break; @@ -386,7 +398,7 @@ class ModList { if (skip) continue; // Create a new order with the mod moved - List newOrder = List.from(bestOrder); + List newOrder = List.from(loadOrder.loadOrder); newOrder.removeAt(i); newOrder.insert(newPos, modId); @@ -398,7 +410,8 @@ class ModList { if (newScore > bestScore) { bestScore = newScore; - bestOrder = newOrder; + loadOrder.loadOrder.clear(); + loadOrder.loadOrder.addAll(newOrder); improved = true; break; // Break inner loop, move to next mod } @@ -411,17 +424,18 @@ class ModList { } // After optimizing for soft constraints, sort by size where possible - return _sortSizeWithinConstraints(bestOrder); + return _sortSizeWithinConstraints(loadOrder: loadOrder); } /// Sort mods by size within compatible groups - List _sortSizeWithinConstraints(List order) { + LoadOrder _sortSizeWithinConstraints({LoadOrder? loadOrder}) { + loadOrder ??= LoadOrder(); // Find groups of mods that can be reordered without breaking constraints List> groups = []; List currentGroup = []; - for (int i = 0; i < order.length; i++) { - String modId = order[i]; + for (int i = 0; i < loadOrder.loadOrder.length; i++) { + String modId = loadOrder.loadOrder[i]; Mod mod = mods[modId]!; if (currentGroup.isEmpty) { @@ -475,43 +489,55 @@ class ModList { } // Reconstruct the order - List result = []; + loadOrder.loadOrder.clear(); for (List group in groups) { - result.addAll(group); + loadOrder.loadOrder.addAll(group); } - return result; + return loadOrder; } - List loadDependencies( + LoadOrder loadDependencies( String modId, [ + LoadOrder? loadOrder, List? toEnable, Map? seen, ]) { final mod = mods[modId]!; + loadOrder ??= LoadOrder(); toEnable ??= []; seen ??= {}; + for (final dep in mod.dependencies) { + if (!mods.containsKey(dep)) { + loadOrder.errors.add( + 'Missing dependency: ${mod.name} requires mod with ID $dep', + ); + continue; + } final depMod = mods[dep]!; if (seen[dep] == true) { - throw Exception('Cyclic dependency detected: $modId -> $dep'); + loadOrder.errors.add('Cyclic dependency detected: $modId -> $dep'); + continue; } seen[dep] = true; toEnable.add(depMod.id); - loadDependencies(depMod.id, toEnable, seen); + loadDependencies(depMod.id, loadOrder, toEnable, seen); } - return toEnable; + + return loadOrder; } - List loadRequired() { + LoadOrder loadRequired([LoadOrder? loadOrder]) { + loadOrder ??= LoadOrder(); final toEnable = []; for (final modid in activeMods.keys) { - loadDependencies(modid, toEnable); + loadDependencies(modid, loadOrder, toEnable); } for (final modid in toEnable) { setEnabled(modid, true); } - return generateLoadOrder(); + return generateLoadOrder(loadOrder); } ModList copyWith({ diff --git a/lib/mod_list_troubleshooter.dart b/lib/mod_list_troubleshooter.dart index e4ad8dc..0ea2a75 100644 --- a/lib/mod_list_troubleshooter.dart +++ b/lib/mod_list_troubleshooter.dart @@ -1,7 +1,3 @@ -import 'dart:async'; - -import 'package:rimworld_modman/logger.dart'; -import 'package:rimworld_modman/mod.dart'; import 'package:rimworld_modman/mod_list.dart'; /// A class that helps find the minimum set of mods that exhibit a bug. diff --git a/test/mod_list_test.dart b/test/mod_list_test.dart index 712ec7d..e4915f0 100644 --- a/test/mod_list_test.dart +++ b/test/mod_list_test.dart @@ -8,6 +8,13 @@ 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', @@ -40,8 +47,9 @@ void main() { list.enableAll(); final order = list.generateLoadOrder(); - final harmonyIndex = order.indexOf('harmony'); - final rimworldIndex = order.indexOf('ludeon.rimworld'); + final harmonyIndex = order.loadOrder.indexOf('harmony'); + final rimworldIndex = order.loadOrder.indexOf('ludeon.rimworld'); + expect(order.errors, isEmpty); expect(harmonyIndex, lessThan(rimworldIndex)); }); @@ -67,9 +75,10 @@ void main() { list.enableAll(); final order = list.generateLoadOrder(); - final prepatcherIndex = order.indexOf('prepatcher'); - final harmonyIndex = order.indexOf('harmony'); - final rimworldIndex = order.indexOf('ludeon.rimworld'); + final prepatcherIndex = order.loadOrder.indexOf('prepatcher'); + final harmonyIndex = order.loadOrder.indexOf('harmony'); + final rimworldIndex = order.loadOrder.indexOf('ludeon.rimworld'); + expect(order.errors, isEmpty); expect(prepatcherIndex, greaterThan(harmonyIndex)); expect(prepatcherIndex, greaterThan(rimworldIndex)); }); @@ -89,8 +98,9 @@ void main() { list.enableAll(); final order = list.generateLoadOrder(); - final rimworldIndex = order.indexOf('ludeon.rimworld'); - final anomalyIndex = order.indexOf('ludeon.rimworld.anomaly'); + final rimworldIndex = order.loadOrder.indexOf('ludeon.rimworld'); + final anomalyIndex = order.loadOrder.indexOf('ludeon.rimworld.anomaly'); + expect(order.errors, isEmpty); expect(rimworldIndex, lessThan(anomalyIndex)); }); @@ -105,7 +115,7 @@ void main() { list.disableAll(); final order = list.generateLoadOrder(); - final disabledIndex = order.indexOf('disabledDummy'); + final disabledIndex = order.loadOrder.indexOf('disabledDummy'); expect(disabledIndex, isNegative); }); @@ -122,12 +132,13 @@ void main() { list.enableAll(); final order = list.generateLoadOrder(); - final smolIndex = order.indexOf('smol'); - final yuuugeIndex = order.indexOf('yuuuge'); + final smolIndex = order.loadOrder.indexOf('smol'); + final yuuugeIndex = order.loadOrder.indexOf('yuuuge'); + expect(order.errors, isEmpty); expect(yuuugeIndex, lessThan(smolIndex)); }); - test('Incompatible mods should throw exception', () { + test('Incompatible mods should return errors', () { final list = ModList(); list.mods = { 'incompatible': makeDummy().copyWith( @@ -138,7 +149,10 @@ void main() { 'harmony': makeDummy().copyWith(name: 'Harmony', id: 'harmony'), }; list.enableAll(); - expect(() => list.generateLoadOrder(), throwsException); + 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); }); }); @@ -156,7 +170,8 @@ void main() { list.disableAll(); list.setEnabled('prepatcher', true); final order = list.loadRequired(); - expect(order.indexOf('harmony'), isNot(-1)); + expect(order.errors, isEmpty); + expect(order.loadOrder.indexOf('harmony'), isNot(-1)); }); test('Only required mods should be enabled', () { @@ -173,11 +188,12 @@ void main() { list.disableAll(); list.setEnabled('prepatcher', true); final order = list.loadRequired(); - expect(order.indexOf('harmony'), isNot(-1)); - expect(order.indexOf('dummy'), -1); + expect(order.errors, isEmpty); + expect(order.loadOrder.indexOf('harmony'), isNot(-1)); + expect(order.loadOrder.indexOf('dummy'), -1); }); - test('Incompatible mods should throw exception', () { + test('Incompatible mods should return errors', () { final list = ModList(); list.mods = { 'incompatible': makeDummy().copyWith( @@ -195,7 +211,10 @@ void main() { list.disableAll(); list.setEnabled('incompatible', true); list.setEnabled('prepatcher', true); - expect(() => list.loadRequired(), throwsException); + final result = list.loadRequired(); + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('incompatible')), isTrue); + expect(result.errors.any((e) => e.contains('harmony')), isTrue); }); test('Dependencies of dependencies should be loaded', () { final list = ModList(); @@ -215,14 +234,15 @@ void main() { 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)); + expect(order.errors, isEmpty); + expect(order.loadOrder.indexOf('modA'), isNot(-1)); + expect(order.loadOrder.indexOf('modB'), isNot(-1)); + expect(order.loadOrder.indexOf('modC'), isNot(-1)); }); }); group('Test cyclic dependencies', () { - test('Cyclic dependencies should throw exception', () { + test('Cyclic dependencies should return errors', () { final list = ModList(); list.mods = { 'modA': makeDummy().copyWith( @@ -243,7 +263,11 @@ void main() { }; list.disableAll(); list.setEnabled('modA', true); - expect(() => list.loadRequired(), throwsException); + final result = list.loadRequired(); + 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); }); }); @@ -264,10 +288,11 @@ void main() { list.enableAll(); final order = list.generateLoadOrder(); - final aIndex = order.indexOf('modA'); - final bIndex = order.indexOf('modB'); - final cIndex = order.indexOf('modC'); + final aIndex = order.loadOrder.indexOf('modA'); + final bIndex = order.loadOrder.indexOf('modB'); + final cIndex = order.loadOrder.indexOf('modC'); + expect(order.errors, isEmpty); expect(aIndex, greaterThan(bIndex)); expect(aIndex, lessThan(cIndex)); }); @@ -286,7 +311,9 @@ void main() { 'modC': makeDummy().copyWith(name: 'Mod C', id: 'modC'), }; list.enableAll(); - final conflicts = list.checkIncompatibilities(); + final conflicts = list.checkIncompatibilities( + list.activeMods.keys.toList(), + ); expect(conflicts.length, equals(2)); // Check if conflicts contain these pairs (order doesn't matter) @@ -349,8 +376,9 @@ void main() { final order = list.generateLoadOrder(); // Base game should load before any expansions - final baseGameIndex = order.indexOf('ludeon.rimworld'); - final expansionIndex = order.indexOf('ludeon.rimworld.anomaly'); + final baseGameIndex = order.loadOrder.indexOf('ludeon.rimworld'); + final expansionIndex = order.loadOrder.indexOf('ludeon.rimworld.anomaly'); + expect(order.errors, isEmpty); expect(baseGameIndex, lessThan(expansionIndex)); }); }); @@ -381,16 +409,268 @@ void main() { final result = list.loadRequired(); + expect(result.errors, isEmpty); // All mods in the chain should be enabled - expect(result.contains('modA'), isTrue); - expect(result.contains('modB'), isTrue); - expect(result.contains('modC'), isTrue); - expect(result.contains('modD'), isTrue); + expect(result.loadOrder.contains('modA'), isTrue); + expect(result.loadOrder.contains('modB'), isTrue); + expect(result.loadOrder.contains('modC'), isTrue); + expect(result.loadOrder.contains('modD'), isTrue); // The order should be D -> C -> B -> A - expect(result.indexOf('modD'), lessThan(result.indexOf('modC'))); - expect(result.indexOf('modC'), lessThan(result.indexOf('modB'))); - expect(result.indexOf('modB'), lessThan(result.indexOf('modA'))); + expect( + result.loadOrder.indexOf('modD'), + lessThan(result.loadOrder.indexOf('modC')), + ); + expect( + result.loadOrder.indexOf('modC'), + lessThan(result.loadOrder.indexOf('modB')), + ); + expect( + result.loadOrder.indexOf('modB'), + lessThan(result.loadOrder.indexOf('modA')), + ); + }); + }); + + 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(); + + // Should return errors with both missing dependencies mentioned + final result = list.generateLoadOrder(); + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('missing1')), isTrue); + expect(result.errors.any((e) => e.contains('missing2')), isTrue); + }); + }); + + 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(); + expect(order.errors, isEmpty); + expect(order.loadOrder.contains('modA'), isTrue); + }); + + 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(); + expect(order.errors, isEmpty); + expect(order.loadOrder.contains('modA'), isTrue); + }); + }); + + 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(); + 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); + }); + + 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(); + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('missingDep')), isTrue); + expect(result.errors.any((e) => e.contains('Incompatible')), isTrue); + }); + }); + + 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(); + + expect(result.errors, isEmpty); + expect(result.loadOrder.length, equals(2)); + expect(result.loadOrder.contains('modA'), isTrue); + expect(result.loadOrder.contains('modB'), isTrue); + }); + + 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(); + + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('nonExistentMod')), isTrue); + // The load order might be empty or contain valid mods only + }); + + 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(); + + expect(result.errors, isNotEmpty); + expect(result.errors.any((e) => e.contains('nonExistentMod')), isTrue); + // modA should still be in the load order even though modB has errors + expect(result.loadOrder.contains('modA'), isTrue); + // modB might be excluded from load order due to its error }); }); } diff --git a/test/mod_list_troubleshooter_test.dart b/test/mod_list_troubleshooter_test.dart index b70339e..c297bd3 100644 --- a/test/mod_list_troubleshooter_test.dart +++ b/test/mod_list_troubleshooter_test.dart @@ -710,9 +710,9 @@ void main() { var result = troubleshooter.linearForward(stepSize: 10); var loadOrder = result.loadRequired(); - expect(loadOrder.length, equals(11)); + expect(loadOrder.loadOrder.length, equals(11)); for (int i = 0; i < expectedFirst.length; i++) { - expect(loadOrder[i], equals(expectedFirst[i])); + expect(loadOrder.loadOrder[i], equals(expectedFirst[i])); } final expectedSecond = [ @@ -733,9 +733,9 @@ void main() { result = troubleshooter.linearForward(stepSize: 10); loadOrder = result.loadRequired(); - expect(loadOrder.length, equals(10)); + expect(loadOrder.loadOrder.length, equals(10)); for (int i = 0; i < expectedSecond.length; i++) { - expect(loadOrder[i], equals(expectedSecond[i])); + expect(loadOrder.loadOrder[i], equals(expectedSecond[i])); } }); });