306 lines
9.2 KiB
Dart
306 lines
9.2 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:uto_manager/domain/services/doctrinal_templates.dart';
|
|
import 'package:uto_manager/domain/services/template_instantiator.dart';
|
|
|
|
void main() {
|
|
// Use a deterministic counter-based ID generator for tests.
|
|
var counter0 = 0;
|
|
String nextId() => 'id-${counter0++}';
|
|
|
|
late TemplateInstantiator instantiator;
|
|
|
|
setUp(() {
|
|
counter0 = 0;
|
|
instantiator = TemplateInstantiator(idGenerator: nextId);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Basic instantiation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
group('basic instantiation', () {
|
|
test('instantiates a single root unit with no children or slots', () {
|
|
const def = UnitDefinition(
|
|
name: 'Test Brigade',
|
|
echelon: 'brigade',
|
|
branch: 'infantry',
|
|
);
|
|
|
|
final result = instantiator.instantiate(def);
|
|
|
|
expect(result.units.length, 1);
|
|
expect(result.slots, isEmpty);
|
|
expect(result.units.first.name, 'Test Brigade');
|
|
expect(result.units.first.parentUnitId, isNull);
|
|
});
|
|
|
|
test('root unit has no parent', () {
|
|
const def = UnitDefinition(
|
|
name: 'Root',
|
|
echelon: 'brigade',
|
|
branch: 'infantry',
|
|
);
|
|
|
|
final result = instantiator.instantiate(def);
|
|
expect(result.units.first.isRoot, isTrue);
|
|
});
|
|
|
|
test('instantiates slots for root unit', () {
|
|
const def = UnitDefinition(
|
|
name: 'Test Platoon',
|
|
echelon: 'platoon',
|
|
branch: 'infantry',
|
|
slots: [
|
|
SlotDefinition(
|
|
positionTitle: 'Platoon Leader',
|
|
dutyMos: '11A',
|
|
authorizedRank: 'o2',
|
|
isKeyLeader: true,
|
|
),
|
|
SlotDefinition(
|
|
positionTitle: 'Platoon Sergeant',
|
|
dutyMos: '11B',
|
|
authorizedRank: 'e7',
|
|
isKeyLeader: true,
|
|
),
|
|
],
|
|
);
|
|
|
|
final result = instantiator.instantiate(def);
|
|
|
|
expect(result.units.length, 1);
|
|
expect(result.slots.length, 2);
|
|
expect(result.slots.first.positionTitle, 'Platoon Leader');
|
|
expect(result.slots.first.isKeyLeader, isTrue);
|
|
expect(result.slots.first.unitId, result.units.first.unitId);
|
|
});
|
|
|
|
test('all slots reference the correct unit ID', () {
|
|
const def = UnitDefinition(
|
|
name: 'Squad',
|
|
echelon: 'squad',
|
|
branch: 'infantry',
|
|
slots: [
|
|
SlotDefinition(
|
|
positionTitle: 'Squad Leader',
|
|
dutyMos: '11B',
|
|
authorizedRank: 'e6',
|
|
),
|
|
SlotDefinition(
|
|
positionTitle: 'Rifleman',
|
|
dutyMos: '11B',
|
|
authorizedRank: 'e3',
|
|
),
|
|
],
|
|
);
|
|
|
|
final result = instantiator.instantiate(def);
|
|
final unitId = result.units.first.unitId;
|
|
|
|
for (final slot in result.slots) {
|
|
expect(slot.unitId, unitId);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Parent-child relationships
|
|
// ---------------------------------------------------------------------------
|
|
|
|
group('parent-child relationships', () {
|
|
test('child unit has correct parentUnitId', () {
|
|
const def = UnitDefinition(
|
|
name: 'Battalion',
|
|
echelon: 'battalion',
|
|
branch: 'infantry',
|
|
children: [
|
|
UnitDefinition(
|
|
name: 'Alpha Company',
|
|
echelon: 'company',
|
|
branch: 'infantry',
|
|
),
|
|
],
|
|
);
|
|
|
|
final result = instantiator.instantiate(def);
|
|
|
|
expect(result.units.length, 2);
|
|
final parent = result.units.firstWhere((u) => u.name == 'Battalion');
|
|
final child = result.units.firstWhere((u) => u.name == 'Alpha Company');
|
|
expect(child.parentUnitId, parent.unitId);
|
|
});
|
|
|
|
test('deeply nested tree has correct parent chain', () {
|
|
const def = UnitDefinition(
|
|
name: 'Brigade',
|
|
echelon: 'brigade',
|
|
branch: 'infantry',
|
|
children: [
|
|
UnitDefinition(
|
|
name: 'Battalion',
|
|
echelon: 'battalion',
|
|
branch: 'infantry',
|
|
children: [
|
|
UnitDefinition(
|
|
name: 'Company',
|
|
echelon: 'company',
|
|
branch: 'infantry',
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
|
|
final result = instantiator.instantiate(def);
|
|
|
|
expect(result.units.length, 3);
|
|
final brigade = result.units.firstWhere((u) => u.name == 'Brigade');
|
|
final battalion = result.units.firstWhere((u) => u.name == 'Battalion');
|
|
final company = result.units.firstWhere((u) => u.name == 'Company');
|
|
|
|
expect(battalion.parentUnitId, brigade.unitId);
|
|
expect(company.parentUnitId, battalion.unitId);
|
|
});
|
|
|
|
test('all generated IDs are unique', () {
|
|
// Use UUID-style generator to verify uniqueness.
|
|
var counter = 0;
|
|
final inst = TemplateInstantiator(idGenerator: () => 'uid-${counter++}');
|
|
|
|
const def = UnitDefinition(
|
|
name: 'Brigade',
|
|
echelon: 'brigade',
|
|
branch: 'infantry',
|
|
slots: [
|
|
SlotDefinition(
|
|
positionTitle: 'Commander',
|
|
dutyMos: '11A',
|
|
authorizedRank: 'o6',
|
|
),
|
|
],
|
|
children: [
|
|
UnitDefinition(
|
|
name: 'Battalion',
|
|
echelon: 'battalion',
|
|
branch: 'infantry',
|
|
),
|
|
],
|
|
);
|
|
|
|
final result = inst.instantiate(def);
|
|
final allIds = [
|
|
...result.units.map((u) => u.unitId),
|
|
...result.slots.map((s) => s.slotId),
|
|
];
|
|
|
|
expect(allIds.toSet().length, allIds.length);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Doctrinal templates
|
|
// ---------------------------------------------------------------------------
|
|
|
|
group('doctrinal templates', () {
|
|
test('MBCT instantiates with units and slots', () {
|
|
final result = instantiator.instantiate(DoctrinalTemplates.mbct);
|
|
|
|
expect(result.units, isNotEmpty);
|
|
expect(result.slots, isNotEmpty);
|
|
// MBCT has a brigade root
|
|
expect(
|
|
result.units.any((u) => u.name == 'Mobile Brigade Combat Team'),
|
|
isTrue,
|
|
);
|
|
});
|
|
|
|
test('MBCT root unit has no parent', () {
|
|
final result = instantiator.instantiate(DoctrinalTemplates.mbct);
|
|
final roots = result.units.where((u) => u.parentUnitId == null).toList();
|
|
expect(roots.length, 1);
|
|
expect(roots.first.name, 'Mobile Brigade Combat Team');
|
|
});
|
|
|
|
test('SBCT instantiates with units and slots', () {
|
|
final result = instantiator.instantiate(DoctrinalTemplates.sbct);
|
|
expect(result.units, isNotEmpty);
|
|
expect(result.slots, isNotEmpty);
|
|
});
|
|
|
|
test('ABCT instantiates with units and slots', () {
|
|
final result = instantiator.instantiate(DoctrinalTemplates.abct);
|
|
expect(result.units, isNotEmpty);
|
|
expect(result.slots, isNotEmpty);
|
|
});
|
|
|
|
test('CAB Heavy instantiates with units and slots', () {
|
|
final result = instantiator.instantiate(DoctrinalTemplates.cabHeavy);
|
|
expect(result.units, isNotEmpty);
|
|
expect(result.slots, isNotEmpty);
|
|
});
|
|
|
|
test('CAB Light instantiates with units and slots', () {
|
|
final result = instantiator.instantiate(DoctrinalTemplates.cabLight);
|
|
expect(result.units, isNotEmpty);
|
|
expect(result.slots, isNotEmpty);
|
|
});
|
|
|
|
test('all template units have valid parent references', () {
|
|
for (final entry in DoctrinalTemplates.all.entries) {
|
|
final result = instantiator.instantiate(entry.value);
|
|
final unitIds = result.units.map((u) => u.unitId).toSet();
|
|
|
|
for (final unit in result.units) {
|
|
if (unit.parentUnitId != null) {
|
|
expect(
|
|
unitIds.contains(unit.parentUnitId),
|
|
isTrue,
|
|
reason: '${entry.key}: unit "${unit.name}" references unknown parent '
|
|
'${unit.parentUnitId}',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
test('all template slots reference existing units', () {
|
|
for (final entry in DoctrinalTemplates.all.entries) {
|
|
final result = instantiator.instantiate(entry.value);
|
|
final unitIds = result.units.map((u) => u.unitId).toSet();
|
|
|
|
for (final slot in result.slots) {
|
|
expect(
|
|
unitIds.contains(slot.unitId),
|
|
isTrue,
|
|
reason: '${entry.key}: slot "${slot.positionTitle}" references unknown '
|
|
'unit ${slot.unitId}',
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('MBCT has key leader slots', () {
|
|
final result = instantiator.instantiate(DoctrinalTemplates.mbct);
|
|
expect(result.slots.any((s) => s.isKeyLeader), isTrue);
|
|
});
|
|
|
|
test('DoctrinalTemplates.all contains all five templates', () {
|
|
expect(
|
|
DoctrinalTemplates.all.keys,
|
|
containsAll([
|
|
'mbct',
|
|
'sbct',
|
|
'abct',
|
|
'cab_heavy',
|
|
'cab_light',
|
|
]),);
|
|
});
|
|
|
|
test('DoctrinalTemplates.displayName returns non-empty strings', () {
|
|
for (final key in DoctrinalTemplates.all.keys) {
|
|
expect(DoctrinalTemplates.displayName(key), isNotEmpty);
|
|
}
|
|
});
|
|
});
|
|
}
|