Files
plezy/test/providers/theme_provider_test.dart
T

171 lines
5.8 KiB
Dart

import 'package:flutter/material.dart' as material;
import 'package:flutter_test/flutter_test.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:plezy/providers/theme_provider.dart';
import 'package:plezy/services/base_shared_preferences_service.dart';
import 'package:plezy/services/settings_service.dart' as settings;
import '../test_helpers/prefs.dart';
void main() {
setUp(resetSharedPreferencesForTest);
group('ThemeProvider', () {
test('exposes a non-null themeMode immediately and flips after init', () async {
final p = ThemeProvider();
// The constructor synchronously sets _themeMode to system.
expect(p.themeMode, settings.ThemeMode.system);
// After init the value reflects what's persisted (default).
// First-run default is computed by the EnumPref; it is one of the four
// ThemeMode values regardless of whether we're on TV or not.
// Wait a microtask for _initializeSettings() to finish.
await Future.delayed(Duration.zero);
expect(settings.ThemeMode.values, contains(p.themeMode));
p.dispose();
});
test('setThemeMode persists, notifies, and is a no-op for same value', () async {
final p = ThemeProvider();
await Future.delayed(Duration.zero);
var notified = 0;
p.addListener(() => notified++);
// Pick a mode that is guaranteed different from default `system`/`oled`
// on either pathway: cycle through values until we hit one that differs.
final initial = p.themeMode;
final next = settings.ThemeMode.values.firstWhere((m) => m != initial);
await p.setThemeMode(next);
expect(p.themeMode, next);
expect(notified, 1);
// Same value → no notify.
await p.setThemeMode(next);
expect(notified, 1);
// Verify persisted via SettingsService.
final svc = await settings.SettingsService.getInstance();
expect(svc.read(settings.SettingsService.themeMode), next);
p.dispose();
});
test('materialThemeMode maps each ThemeMode value correctly', () async {
final p = ThemeProvider();
await Future.delayed(Duration.zero);
await p.setThemeMode(settings.ThemeMode.light);
expect(p.materialThemeMode, material.ThemeMode.light);
await p.setThemeMode(settings.ThemeMode.dark);
expect(p.materialThemeMode, material.ThemeMode.dark);
await p.setThemeMode(settings.ThemeMode.oled);
expect(p.materialThemeMode, material.ThemeMode.dark);
await p.setThemeMode(settings.ThemeMode.system);
expect(p.materialThemeMode, material.ThemeMode.system);
p.dispose();
});
test('isDarkMode reflects mode for explicit settings', () async {
final p = ThemeProvider();
await Future.delayed(Duration.zero);
await p.setThemeMode(settings.ThemeMode.light);
expect(p.isDarkMode, isFalse);
await p.setThemeMode(settings.ThemeMode.dark);
expect(p.isDarkMode, isTrue);
await p.setThemeMode(settings.ThemeMode.oled);
expect(p.isDarkMode, isTrue);
p.dispose();
});
test('darkTheme variants differ between dark and oled', () async {
final p = ThemeProvider();
await Future.delayed(Duration.zero);
await p.setThemeMode(settings.ThemeMode.dark);
final dark = p.darkTheme;
await p.setThemeMode(settings.ThemeMode.oled);
final oled = p.darkTheme;
// OLED must be a true black canvas; regular dark uses a non-black surface.
expect(oled.scaffoldBackgroundColor, const material.Color(0xFF000000));
expect(dark.scaffoldBackgroundColor, isNot(const material.Color(0xFF000000)));
p.dispose();
});
test('themeModeDisplayName and themeModeIcon match the active mode', () async {
final p = ThemeProvider();
await Future.delayed(Duration.zero);
await p.setThemeMode(settings.ThemeMode.light);
expect(p.themeModeDisplayName, 'Light');
expect(p.themeModeIcon, Symbols.light_mode_rounded);
await p.setThemeMode(settings.ThemeMode.dark);
expect(p.themeModeDisplayName, 'Dark');
expect(p.themeModeIcon, Symbols.dark_mode_rounded);
await p.setThemeMode(settings.ThemeMode.oled);
expect(p.themeModeDisplayName, 'OLED');
expect(p.themeModeIcon, Symbols.contrast_rounded);
await p.setThemeMode(settings.ThemeMode.system);
expect(p.themeModeDisplayName, 'System');
expect(p.themeModeIcon, Symbols.brightness_auto_rounded);
p.dispose();
});
test('reload re-reads after external mutation', () async {
final p = ThemeProvider();
await Future.delayed(Duration.zero);
// Mutate the persisted theme out-of-band.
final svc = await settings.SettingsService.getInstance();
await svc.write(settings.SettingsService.themeMode, settings.ThemeMode.light);
var notified = 0;
p.addListener(() => notified++);
await p.reload();
expect(p.themeMode, settings.ThemeMode.light);
expect(notified, 1);
p.dispose();
});
test('persists across provider instances via SharedPreferences', () async {
final first = ThemeProvider();
await Future.delayed(Duration.zero);
await first.setThemeMode(settings.ThemeMode.dark);
first.dispose();
// Reset only the cached singleton — backing store is preserved.
BaseSharedPreferencesService.resetForTesting();
final second = ThemeProvider();
await Future.delayed(Duration.zero);
expect(second.themeMode, settings.ThemeMode.dark);
second.dispose();
});
test('safeNotifyListeners no-ops after dispose', () async {
final p = ThemeProvider();
await Future.delayed(Duration.zero);
p.dispose();
// Should not throw — reload calls safeNotifyListeners under the hood.
await p.reload();
});
});
}