301 lines
9.4 KiB
Dart
301 lines
9.4 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:uto_manager/domain/entities/doctrinal_branch.dart';
|
|
import 'package:uto_manager/domain/entities/echelon.dart';
|
|
import 'package:uto_manager/domain/entities/position_slot.dart';
|
|
import 'package:uto_manager/domain/entities/unit_node.dart';
|
|
import 'package:uto_manager/domain/value_objects/duty_mos.dart';
|
|
import 'package:uto_manager/domain/entities/authorized_rank.dart';
|
|
import 'package:uto_manager/infrastructure/codecs/pdf_chart_exporter.dart';
|
|
|
|
void main() {
|
|
const exporter = PdfChartExporter();
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
UnitNode _makeUnit(
|
|
String id,
|
|
Echelon echelon, {
|
|
String? parentId,
|
|
String name = '',
|
|
}) =>
|
|
UnitNode(
|
|
unitId: id,
|
|
echelon: echelon,
|
|
branch: DoctrinalBranch.infantry,
|
|
parentUnitId: parentId,
|
|
name: name.isEmpty ? 'Unit $id' : name,
|
|
isKeyLeader: false,
|
|
);
|
|
|
|
PositionSlot _makeSlot(String id, String unitId) => PositionSlot(
|
|
slotId: id,
|
|
unitId: unitId,
|
|
positionTitle: 'Rifleman',
|
|
dutyMos: DutyMos.unvalidated('11B'),
|
|
authorizedRank: AuthorizedRank.e3,
|
|
isKeyLeader: false,
|
|
);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Basic output
|
|
// ---------------------------------------------------------------------------
|
|
|
|
group('PdfChartExporter — basic output', () {
|
|
test('returns non-empty bytes for empty tree', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test Unit',
|
|
);
|
|
expect(bytes, isNotEmpty);
|
|
});
|
|
|
|
test('output starts with PDF magic bytes %PDF-1.4', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test Unit',
|
|
);
|
|
final header = String.fromCharCodes(bytes.take(8));
|
|
expect(header, startsWith('%PDF-1.4'));
|
|
});
|
|
|
|
test('output ends with %%EOF marker', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test Unit',
|
|
);
|
|
final tail = String.fromCharCodes(bytes.skip(bytes.length - 10));
|
|
expect(tail, contains('%%EOF'));
|
|
});
|
|
|
|
test('output contains unit designation on cover page', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: '1st MBCT',
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains('1st MBCT'));
|
|
});
|
|
|
|
test('output contains UNCLASSIFIED classification banner', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test',
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains('UNCLASSIFIED'));
|
|
});
|
|
|
|
test('output contains xref table', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test',
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains('xref'));
|
|
expect(content, contains('trailer'));
|
|
expect(content, contains('startxref'));
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tree rendering
|
|
// ---------------------------------------------------------------------------
|
|
|
|
group('PdfChartExporter — tree rendering', () {
|
|
test('single root unit renders without error', () {
|
|
final units = [_makeUnit('u1', Echelon.platoon, name: '1st Platoon')];
|
|
final bytes = exporter.export(
|
|
units: units,
|
|
slots: const [],
|
|
unitDesignation: '1st Platoon',
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains('1st Platoon'));
|
|
});
|
|
|
|
test('parent-child tree renders connector lines', () {
|
|
final units = [
|
|
_makeUnit('u1', Echelon.platoon, name: '1st Platoon'),
|
|
_makeUnit('u2', Echelon.squad, parentId: 'u1', name: '1st Squad'),
|
|
_makeUnit('u3', Echelon.squad, parentId: 'u1', name: '2nd Squad'),
|
|
];
|
|
final bytes = exporter.export(
|
|
units: units,
|
|
slots: const [],
|
|
unitDesignation: '1st Platoon',
|
|
);
|
|
// Connector lines use 'm' (moveto) and 'l' (lineto) PDF operators.
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains(' m '));
|
|
expect(content, contains(' l S'));
|
|
});
|
|
|
|
test('all echelon levels render without error', () {
|
|
final echelons = Echelon.values;
|
|
final units = echelons
|
|
.asMap()
|
|
.entries
|
|
.map((e) => _makeUnit('u${e.key}', e.value, name: e.value.displayName))
|
|
.toList();
|
|
|
|
expect(
|
|
() => exporter.export(
|
|
units: units,
|
|
slots: const [],
|
|
unitDesignation: 'All Echelons',
|
|
),
|
|
returnsNormally,
|
|
);
|
|
});
|
|
|
|
test('unit names appear in PDF content stream', () {
|
|
final units = [
|
|
_makeUnit('u1', Echelon.company, name: 'Alpha Company'),
|
|
_makeUnit('u2', Echelon.platoon, parentId: 'u1', name: '1st Platoon'),
|
|
];
|
|
final bytes = exporter.export(
|
|
units: units,
|
|
slots: const [],
|
|
unitDesignation: 'Alpha Company',
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
// Names are truncated to 18 chars; both fit.
|
|
expect(content, contains('Alpha Company'));
|
|
expect(content, contains('1st Platoon'));
|
|
});
|
|
|
|
test('slots are accepted without error', () {
|
|
final units = [_makeUnit('u1', Echelon.squad)];
|
|
final slots = List.generate(9, (i) => _makeSlot('sl$i', 'u1'));
|
|
expect(
|
|
() => exporter.export(
|
|
units: units,
|
|
slots: slots,
|
|
unitDesignation: 'Test Squad',
|
|
),
|
|
returnsNormally,
|
|
);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Page sizes
|
|
// ---------------------------------------------------------------------------
|
|
|
|
group('PdfChartExporter — page sizes', () {
|
|
for (final size in PdfPageSize.values) {
|
|
test('${size.name} page size produces valid PDF', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test',
|
|
pageSize: size,
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, startsWith('%PDF-1.4'));
|
|
expect(content, contains('%%EOF'));
|
|
});
|
|
}
|
|
|
|
test('letter page MediaBox contains 612 and 792', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test',
|
|
pageSize: PdfPageSize.letter,
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains('612.0'));
|
|
expect(content, contains('792.0'));
|
|
});
|
|
|
|
test('tabloid page MediaBox contains 792 and 1224', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test',
|
|
pageSize: PdfPageSize.tabloid,
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains('792.0'));
|
|
expect(content, contains('1224.0'));
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Export date
|
|
// ---------------------------------------------------------------------------
|
|
|
|
group('PdfChartExporter — export date', () {
|
|
test('custom export date appears in cover page', () {
|
|
final date = DateTime(2026, 8, 9);
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test',
|
|
exportDate: date,
|
|
);
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains('2026-08-09'));
|
|
});
|
|
|
|
test('defaults to current date when exportDate is null', () {
|
|
final bytes = exporter.export(
|
|
units: const [],
|
|
slots: const [],
|
|
unitDesignation: 'Test',
|
|
);
|
|
final year = DateTime.now().year.toString();
|
|
final content = String.fromCharCodes(bytes);
|
|
expect(content, contains(year));
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Large tree
|
|
// ---------------------------------------------------------------------------
|
|
|
|
group('PdfChartExporter — large tree', () {
|
|
test('50-node tree exports without error', () {
|
|
final units = <UnitNode>[];
|
|
units.add(_makeUnit('root', Echelon.company, name: 'Alpha Co'));
|
|
for (var i = 0; i < 3; i++) {
|
|
units.add(_makeUnit('plt$i', Echelon.platoon, parentId: 'root', name: '${i + 1}st Plt'));
|
|
for (var j = 0; j < 3; j++) {
|
|
units.add(_makeUnit(
|
|
'sq${i}_$j',
|
|
Echelon.squad,
|
|
parentId: 'plt$i',
|
|
name: '${j + 1}st Sq',
|
|
));
|
|
for (var k = 0; k < 2; k++) {
|
|
units.add(_makeUnit(
|
|
'tm${i}_${j}_$k',
|
|
Echelon.team,
|
|
parentId: 'sq${i}_$j',
|
|
name: '${k + 1} Team',
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(
|
|
() => exporter.export(
|
|
units: units,
|
|
slots: const [],
|
|
unitDesignation: 'Alpha Co',
|
|
),
|
|
returnsNormally,
|
|
);
|
|
});
|
|
});
|
|
}
|