Ocideck/test/app_localizations_test.dart
Brenno de Winter 2c4a6f7358
Some checks failed
CI / Format · Analyze · Test (push) Has been cancelled
CI / Format · Analyze · Test (pull_request) Has been cancelled
Sync presenter annotations live, keep styling out of saved .md
Presentation fixes:
- Mirror the in-progress pen/highlighter stroke to the audience window
  live (new 'inkLive' channel) so highlights appear as they are drawn,
  not only after the pen lifts.
- Cover the macOS menu bar on the beamer: raise the audience window
  above .mainMenu level so the Apple/Wi-Fi strip no longer shows during
  a presentation.

Styling no longer lives in the file:
- generateDeck no longer embeds the ThemeProfile; a saved .md holds only
  content. The profile is inlined only for the transient audience-window
  payload (inlineStyleProfile: true), never to disk.
- On open, the app applies the active style profile (FileService.openDeck
  / activeProfileFor, DeckNotifier.loadDeck); applyMarkdown preserves the
  current profile.

Quality pass / tests green:
- Complete the consent-screen translations (English plus 7 missing
  strings per other language).
- Pass the consent gate in widget/ui-scale tests by seeding the consent
  key, so the app shell renders.
- Update markdown round-trip tests for the new default and add coverage
  for live stroke streaming and styling-free saves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:25:05 +02:00

133 lines
4 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ocideck/l10n/app_localizations.dart';
void main() {
tearDown(() => AppLocalizations.setActiveLanguageCode('nl'));
test('supports Frisian and Papiamento language choices', () {
expect(AppLocalizations.languageNames['fy'], 'Frysk');
expect(AppLocalizations.languageNames['pap'], 'Papiamento');
expect(AppLocalizations.supportedLocales, contains(const Locale('fy')));
expect(AppLocalizations.supportedLocales, contains(const Locale('pap')));
});
test('uses app translations while Material falls back safely', () {
AppLocalizations.setActiveLanguageCode('fy');
expect(const AppLocalizations(Locale('en')).t('settings'), 'Ynstellingen');
expect(
const AppLocalizations(Locale('en')).d('Toetsenlegenda'),
'Toetsleginda',
);
expect(AppLocalizations.materialLocaleFor('fy'), const Locale('en'));
AppLocalizations.setActiveLanguageCode('pap');
expect(
const AppLocalizations(Locale('en')).t('settings'),
'Preferensianan',
);
expect(
const AppLocalizations(Locale('en')).d('Toetsenlegenda'),
'Legenda di tekla',
);
expect(AppLocalizations.materialLocaleFor('pap'), const Locale('en'));
});
test('all literal Dutch source strings have an English fallback', () {
AppLocalizations.setActiveLanguageCode('en');
const unchangedInEnglish = {
'Accent / bullets',
'Bullet',
'Coverflow',
'Label',
'Logo',
'Logo px',
'PREVIEW',
'Preview',
'Privacy',
'SLIDES',
'Slide',
'slide',
'Spider',
};
final expression = RegExp(r'''\.d\(\s*('(?:\\.|[^'])*'|"(?:\\.|[^"])*")''');
final files = Directory('lib')
.listSync(recursive: true)
.whereType<File>()
.where((file) => file.path.endsWith('.dart'));
final sources = <String>{};
for (final file in files) {
final content = file.readAsStringSync();
for (final match in expression.allMatches(content)) {
sources.add(_unquoteDartString(match.group(1)!));
}
}
final english = const AppLocalizations(Locale('en'));
final missing = sources.where((source) {
final translated = english.d(source);
return translated == source && !unchangedInEnglish.contains(source);
}).toList()..sort();
expect(missing, isEmpty);
});
test('all literal Dutch source strings are translated in every language', () {
const unchangedInAllLanguages = {
'Accent / bullets',
'Bullet',
'Coverflow',
'Label',
'Logo',
'Logo px',
'PREVIEW',
'Preview',
'SLIDES',
'Slide',
'slide',
};
final expression = RegExp(r'''\.d\(\s*('(?:\\.|[^'])*'|"(?:\\.|[^"])*")''');
final files = Directory('lib')
.listSync(recursive: true)
.whereType<File>()
.where((file) => file.path.endsWith('.dart'));
final sources = <String>{};
for (final file in files) {
final content = file.readAsStringSync();
for (final match in expression.allMatches(content)) {
sources.add(_unquoteDartString(match.group(1)!));
}
}
final missingByLanguage = <String, List<String>>{};
for (final languageCode in AppLocalizations.languageNames.keys) {
if (languageCode == 'nl') continue;
final missing = sources.where((source) {
if (unchangedInAllLanguages.contains(source)) return false;
return !AppLocalizations.hasDirectDutchSourceTranslation(
languageCode,
source,
);
}).toList()..sort();
if (missing.isNotEmpty) missingByLanguage[languageCode] = missing;
}
expect(missingByLanguage, isEmpty);
});
}
String _unquoteDartString(String value) {
final quote = value[0];
final body = value.substring(1, value.length - 1);
return body
.replaceAll(r'\\', r'\')
.replaceAll('\\$quote', quote)
.replaceAll(r'\n', '\n')
.replaceAll(r'\r', '\r')
.replaceAll(r'\t', '\t');
}