84a638a41f
Three classes:
1. album_cover_cache.dart docstring used <applicationCacheDirectory>
and <albumId> which the analyzer reads as unintended HTML. Wrapped
the path in backticks and used {} placeholders.
2. settings AppearanceSection used RadioListTile.groupValue + onChanged,
deprecated in Flutter 3.32+. Wrapped the radios in a RadioGroup
ancestor (the new API) so individual tiles only declare value +
activeColor. Test updated to read groupValue off the RadioGroup
ancestor.
3. theme_extension.dart factories built FabledSwordTheme(...) without
const, even though all args are static const tokens. Added const to
the outer constructor calls in both .dark() and .light() — this
propagates const to the inner TextStyle(...) calls automatically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.7 KiB
Dart
83 lines
2.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:mocktail/mocktail.dart';
|
|
|
|
import 'package:minstrel/auth/auth_provider.dart';
|
|
import 'package:minstrel/settings/settings_screen.dart';
|
|
import 'package:minstrel/theme/theme_data.dart';
|
|
import 'package:minstrel/theme/theme_mode_provider.dart';
|
|
|
|
class _MockStorage extends Mock implements FlutterSecureStorage {}
|
|
|
|
class _StubTheme extends ThemeModeController {
|
|
_StubTheme(this._initial);
|
|
final AppThemeMode _initial;
|
|
@override
|
|
Future<AppThemeMode> build() async => _initial;
|
|
}
|
|
|
|
void main() {
|
|
late _MockStorage storage;
|
|
|
|
setUpAll(() {
|
|
registerFallbackValue('');
|
|
});
|
|
|
|
setUp(() {
|
|
storage = _MockStorage();
|
|
when(() => storage.read(key: any(named: 'key'))).thenAnswer((_) async => null);
|
|
when(() => storage.write(key: any(named: 'key'), value: any(named: 'value')))
|
|
.thenAnswer((_) async {});
|
|
});
|
|
|
|
testWidgets('renders three appearance radios + reflects the current mode', (t) async {
|
|
await t.pumpWidget(ProviderScope(
|
|
overrides: [
|
|
secureStorageProvider.overrideWithValue(storage),
|
|
themeModeProvider.overrideWith(() => _StubTheme(AppThemeMode.dark)),
|
|
],
|
|
child: MaterialApp(
|
|
theme: buildDarkTheme(),
|
|
home: const SettingsScreen(),
|
|
),
|
|
));
|
|
await t.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('appearance_system')), findsOneWidget);
|
|
expect(find.byKey(const Key('appearance_light')), findsOneWidget);
|
|
expect(find.byKey(const Key('appearance_dark')), findsOneWidget);
|
|
|
|
// The RadioGroup ancestor owns groupValue post-Flutter-3.32; the
|
|
// selected mode is read off it rather than off individual tiles.
|
|
final group = t.widget<RadioGroup<AppThemeMode>>(
|
|
find.byType(RadioGroup<AppThemeMode>),
|
|
);
|
|
expect(group.groupValue, AppThemeMode.dark);
|
|
final darkRadio = t.widget<RadioListTile<AppThemeMode>>(
|
|
find.byKey(const Key('appearance_dark')),
|
|
);
|
|
expect(darkRadio.value, AppThemeMode.dark);
|
|
});
|
|
|
|
testWidgets('tapping the light radio writes "light" to storage', (t) async {
|
|
await t.pumpWidget(ProviderScope(
|
|
overrides: [
|
|
secureStorageProvider.overrideWithValue(storage),
|
|
themeModeProvider.overrideWith(() => _StubTheme(AppThemeMode.system)),
|
|
],
|
|
child: MaterialApp(
|
|
theme: buildDarkTheme(),
|
|
home: const SettingsScreen(),
|
|
),
|
|
));
|
|
await t.pumpAndSettle();
|
|
|
|
await t.tap(find.byKey(const Key('appearance_light')));
|
|
await t.pumpAndSettle();
|
|
|
|
verify(() => storage.write(key: 'theme_mode', value: 'light')).called(1);
|
|
});
|
|
}
|