test/nav_manifest_test.dart derives docs/nav.generated.json from the _pages list (order = Cmd numbers), the labelOf switch, and both .arb files. On any nav change it regenerates the manifest and fails once with instructions to commit + mirror it to fai_chain/docs/studio/, where the platform repo's docs_consistency gate checks the operator guide against it — cross-repo nav drift becomes a red gate instead of quietly rotting docs. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
118 lines
3.9 KiB
Dart
118 lines
3.9 KiB
Dart
// Nav-manifest guard. The sidebar truth lives in three places in
|
|
// this repo: the `_pages` list (ids + order = Cmd numbers), the
|
|
// `labelOf` switch (id → l10n key), and the two .arb files (key →
|
|
// DE/EN label). The operator guide documenting that nav lives in
|
|
// the PLATFORM repo (fai_chain/docs/studio/operator-guide.md),
|
|
// which this repo's CI cannot see — so the truth is exported as a
|
|
// committed manifest, `docs/nav.generated.json`, and the platform
|
|
// repo's docs_consistency test checks the guide against it.
|
|
//
|
|
// This test derives the manifest from the sources. When it differs
|
|
// from the committed file it REWRITES the file and fails once with
|
|
// instructions — commit the regenerated manifest and mirror it to
|
|
// fai_chain/docs/studio/nav.generated.json so the cross-repo guard
|
|
// stays truthful. Add/rename/reorder a page and forget the guide:
|
|
// the platform gate goes loudly red instead of the docs quietly
|
|
// rotting.
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
const _manifestPath = 'docs/nav.generated.json';
|
|
const _mirrorHint =
|
|
'fai_chain/docs/studio/nav.generated.json (same content)';
|
|
|
|
/// Extract the ordered page ids from the `_pages` list in main.dart.
|
|
List<String> _pageIds(String mainDart) {
|
|
final block = RegExp(
|
|
r'static const _pages = <_NavPage>\[(.*?)\n \];',
|
|
dotAll: true,
|
|
).firstMatch(mainDart);
|
|
if (block == null) {
|
|
fail('could not locate the _pages block in lib/main.dart');
|
|
}
|
|
return RegExp(r"id: '([a-z-]+)'")
|
|
.allMatches(block.group(1)!)
|
|
.map((m) => m.group(1)!)
|
|
.toList();
|
|
}
|
|
|
|
/// Extract the id → l10n-key mapping from the `labelOf` switch.
|
|
Map<String, String> _labelKeys(String mainDart) {
|
|
final keys = <String, String>{};
|
|
for (final m in RegExp(
|
|
r"case '([a-z-]+)':\s*\n\s*return l\.(nav\w+);",
|
|
).allMatches(mainDart)) {
|
|
keys[m.group(1)!] = m.group(2)!;
|
|
}
|
|
if (keys.isEmpty) {
|
|
fail('could not parse the labelOf switch in lib/main.dart');
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
Map<String, dynamic> _arb(String path) =>
|
|
jsonDecode(File(path).readAsStringSync()) as Map<String, dynamic>;
|
|
|
|
void main() {
|
|
test('docs/nav.generated.json matches the sidebar truth', () {
|
|
final mainDart = File('lib/main.dart').readAsStringSync();
|
|
final ids = _pageIds(mainDart);
|
|
final labelKeys = _labelKeys(mainDart);
|
|
final de = _arb('lib/l10n/app_de.arb');
|
|
final en = _arb('lib/l10n/app_en.arb');
|
|
|
|
final pages = <Map<String, dynamic>>[];
|
|
for (var i = 0; i < ids.length; i++) {
|
|
final key = labelKeys[ids[i]];
|
|
expect(
|
|
key,
|
|
isNotNull,
|
|
reason:
|
|
"page '${ids[i]}' has no labelOf case — add it so the "
|
|
'sidebar can localize the label',
|
|
);
|
|
final labelDe = de[key] as String?;
|
|
final labelEn = en[key] as String?;
|
|
expect(
|
|
labelDe,
|
|
isNotNull,
|
|
reason: "l10n key '$key' missing from app_de.arb",
|
|
);
|
|
expect(
|
|
labelEn,
|
|
isNotNull,
|
|
reason: "l10n key '$key' missing from app_en.arb",
|
|
);
|
|
pages.add({
|
|
'id': ids[i],
|
|
'cmd': i + 1,
|
|
'label_de': labelDe,
|
|
'label_en': labelEn,
|
|
});
|
|
}
|
|
|
|
const encoder = JsonEncoder.withIndent(' ');
|
|
final manifest =
|
|
'${encoder.convert({
|
|
'_generated': 'by test/nav_manifest_test.dart — do not edit. '
|
|
'Mirror to $_mirrorHint.',
|
|
'pages': pages,
|
|
})}\n';
|
|
|
|
final file = File(_manifestPath);
|
|
final current = file.existsSync() ? file.readAsStringSync() : null;
|
|
if (current != manifest) {
|
|
file.parent.createSync(recursive: true);
|
|
file.writeAsStringSync(manifest);
|
|
fail(
|
|
'the sidebar nav changed: $_manifestPath was regenerated. '
|
|
'Commit it, mirror it to $_mirrorHint, and update the '
|
|
'operator guide — the platform repo\'s docs_consistency '
|
|
'test checks the guide against this manifest.',
|
|
);
|
|
}
|
|
});
|
|
}
|