Files
clide/test/a11y/i18n_coverage_test.dart
jpmschweitzerandClaude Opus 4.8 b7db22924d feat(theme): canonical theme/namespace lists + ship Summer Night (T-371, T-478)
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>
2026-06-24 15:31:16 +02:00

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');
}
});
});
}