The bundled-theme list and the Tier-0 i18n namespace list were each hand-maintained in 3+ places that had drifted: the testmode harness loaded 8 themes (catppuccin silently unvalidated) while the app and contrast gate loaded 10, and the i18n gate checked 4 of the shipped namespaces. Export one canonical const each — kBundledThemePaths and kTier0Namespaces — and have the app, the testmode harness, and the a11y gates iterate them. Drift-proof meta-assertions: - contrast gate fails if any theme YAML on disk is absent from kBundledThemePaths (so a theme can't sit unvalidated). - i18n gate derives its subjects from assets/i18n/en_us/ at test time and asserts en/nl key parity for every shipped catalog (26, was 4), plus that every kTier0Namespaces entry has a shipped catalog. Surfaced summer-night.yaml: a legacy-ported palette on disk, unbundled and never contrast-checked. Per the maintainer's call, ship it: flesh the palette to full token + syntax coverage (honouring the v1.2 colours, clide derivation pattern for the new keys) and add a structurally identical summer-night-hc sibling that clears the strict extended gate. Both pass. No licenses.yaml change: summer-night is clide's own (ported from legacy clide v1.2 under legacy/, MIT); the only third-party palette, Catppuccin, is already acknowledged; no fonts added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
62 lines
2.7 KiB
Dart
62 lines
2.7 KiB
Dart
import 'dart:io';
|
|
import 'dart:ui';
|
|
|
|
import 'package:clide/kernel/kernel.dart';
|
|
import 'package:flutter/services.dart' show rootBundle;
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
/// i18n coverage gate.
|
|
///
|
|
/// The subject list is derived from the shipped catalogs on disk
|
|
/// (`assets/i18n/en_us/*.json`), NOT a hand-maintained list — so a newly
|
|
/// added catalog is validated automatically and cannot ship unchecked
|
|
/// (T-371; the old gate hand-enumerated 4 of the namespaces and silently
|
|
/// covered less as the app grew).
|
|
///
|
|
/// Per catalog we assert: the en_US catalog loads and is non-empty, and the
|
|
/// nl_NL pack is at exact key parity (no missing or extra keys) — so a locale
|
|
/// switch never falls back to English for a shipped string, and a stray
|
|
/// translation key can't rot unnoticed.
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
const enDir = 'assets/i18n/en_us';
|
|
final namespaces = Directory(
|
|
enDir,
|
|
).listSync().whereType<File>().where((f) => f.path.endsWith('.json')).map((f) => f.uri.pathSegments.last.replaceAll('.json', '')).toList()..sort();
|
|
|
|
group('i18n coverage — every shipped catalog', () {
|
|
test('the asset dir actually ships catalogs', () {
|
|
expect(namespaces, isNotEmpty, reason: 'no catalogs found under $enDir');
|
|
});
|
|
|
|
for (final ns in namespaces) {
|
|
test('$ns: en_US loads non-empty, nl_NL at key parity', () async {
|
|
final loader = AssetCatalogLoader(bundle: rootBundle);
|
|
final en = await loader.load(ns, const Locale('en', 'US'));
|
|
expect(en, isNotEmpty, reason: 'en_US catalog for "$ns" failed to load (asset path wrong?)');
|
|
final nl = await loader.load(ns, const Locale('nl', 'NL'));
|
|
expect(nl, isNotEmpty, reason: 'nl_NL catalog for "$ns" missing or empty');
|
|
|
|
final enKeys = en.keys.toSet();
|
|
final nlKeys = nl.keys.toSet();
|
|
expect(nlKeys.difference(enKeys), isEmpty, reason: 'nl_NL "$ns" has keys absent from en_US: ${nlKeys.difference(enKeys)}');
|
|
expect(enKeys.difference(nlKeys), isEmpty, reason: 'nl_NL "$ns" is missing keys present in en_US: ${enKeys.difference(nlKeys)}');
|
|
});
|
|
}
|
|
});
|
|
|
|
// The Tier-0 preload list (loaded at boot, before its owning extension
|
|
// activates) must name only catalogs that actually ship — else boot preloads
|
|
// a missing namespace. The reverse isn't required: most catalogs load lazily
|
|
// on extension activation, not at boot.
|
|
group('i18n coverage — Tier-0 preload set is honest', () {
|
|
test('every kTier0Namespaces entry has a shipped en_US catalog', () {
|
|
final shipped = namespaces.toSet();
|
|
for (final ns in kTier0Namespaces) {
|
|
expect(shipped.contains(ns), isTrue, reason: 'kTier0Namespaces names "$ns" but no $enDir/$ns.json ships');
|
|
}
|
|
});
|
|
});
|
|
}
|