feat(01-01): add core infrastructure, localization, and Wave 0 tests
- AppDatabase with schemaVersion 1, in-memory executor for testing - Database provider with @Riverpod(keepAlive: true) - AppTheme with sage green seed ColorScheme and warm surface overrides - ThemeNotifier with SharedPreferences persistence, defaults to system - Full German ARB localization (15 keys) with proper umlauts - Minimal main.dart with ProviderScope placeholder - Drift schema v1 captured via make-migrations - All .g.dart files generated via build_runner - Wave 0 tests: database (3), color scheme (6), theme (3), localization (2) -- 14 total, all passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
31
test/core/database/database_test.dart
Normal file
31
test/core/database/database_test.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:household_keeper/core/database/database.dart';
|
||||
|
||||
void main() {
|
||||
group('AppDatabase', () {
|
||||
late AppDatabase db;
|
||||
|
||||
setUp(() {
|
||||
db = AppDatabase(NativeDatabase.memory());
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
test('opens successfully with in-memory executor', () {
|
||||
expect(db, isNotNull);
|
||||
});
|
||||
|
||||
test('has schemaVersion 1', () {
|
||||
expect(db.schemaVersion, equals(1));
|
||||
});
|
||||
|
||||
test('can be closed without error', () async {
|
||||
await db.close();
|
||||
// If we reach here, close succeeded. Re-create for tearDown.
|
||||
db = AppDatabase(NativeDatabase.memory());
|
||||
});
|
||||
});
|
||||
}
|
||||
45
test/core/theme/color_scheme_test.dart
Normal file
45
test/core/theme/color_scheme_test.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:household_keeper/core/theme/app_theme.dart';
|
||||
|
||||
void main() {
|
||||
group('AppTheme ColorScheme', () {
|
||||
test('lightTheme has Brightness.light', () {
|
||||
final theme = AppTheme.lightTheme();
|
||||
expect(theme.colorScheme.brightness, equals(Brightness.light));
|
||||
});
|
||||
|
||||
test('darkTheme has Brightness.dark', () {
|
||||
final theme = AppTheme.darkTheme();
|
||||
expect(theme.colorScheme.brightness, equals(Brightness.dark));
|
||||
});
|
||||
|
||||
test('both themes use sage green seed (primary in green hue range)', () {
|
||||
final lightPrimary = HSLColor.fromColor(
|
||||
AppTheme.lightTheme().colorScheme.primary,
|
||||
);
|
||||
final darkPrimary = HSLColor.fromColor(
|
||||
AppTheme.darkTheme().colorScheme.primary,
|
||||
);
|
||||
|
||||
// Sage green hue is approximately 100-150 degrees
|
||||
expect(lightPrimary.hue, inInclusiveRange(80, 160));
|
||||
expect(darkPrimary.hue, inInclusiveRange(80, 160));
|
||||
});
|
||||
|
||||
test('light surface color is warm stone (0xFFF5F0E8)', () {
|
||||
final theme = AppTheme.lightTheme();
|
||||
expect(theme.colorScheme.surface, equals(const Color(0xFFF5F0E8)));
|
||||
});
|
||||
|
||||
test('dark surface color is warm charcoal (0xFF2A2520), not cold gray', () {
|
||||
final theme = AppTheme.darkTheme();
|
||||
expect(theme.colorScheme.surface, equals(const Color(0xFF2A2520)));
|
||||
});
|
||||
|
||||
test('both themes use Material 3', () {
|
||||
expect(AppTheme.lightTheme().useMaterial3, isTrue);
|
||||
expect(AppTheme.darkTheme().useMaterial3, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
41
test/core/theme/theme_test.dart
Normal file
41
test/core/theme/theme_test.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:household_keeper/core/theme/theme_provider.dart';
|
||||
|
||||
void main() {
|
||||
group('ThemeNotifier', () {
|
||||
setUp(() {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
});
|
||||
|
||||
test('defaults to ThemeMode.system', () {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
final themeMode = container.read(themeProvider);
|
||||
expect(themeMode, equals(ThemeMode.system));
|
||||
});
|
||||
|
||||
test('setThemeMode(dark) updates state to dark', () async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(themeProvider.notifier).setThemeMode(
|
||||
ThemeMode.dark,
|
||||
);
|
||||
expect(container.read(themeProvider), equals(ThemeMode.dark));
|
||||
});
|
||||
|
||||
test('setThemeMode(light) updates state to light', () async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
|
||||
await container.read(themeProvider.notifier).setThemeMode(
|
||||
ThemeMode.light,
|
||||
);
|
||||
expect(container.read(themeProvider), equals(ThemeMode.light));
|
||||
});
|
||||
});
|
||||
}
|
||||
56
test/l10n/localization_test.dart
Normal file
56
test/l10n/localization_test.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:household_keeper/l10n/app_localizations.dart';
|
||||
|
||||
void main() {
|
||||
group('AppLocalizations (German)', () {
|
||||
late AppLocalizations l10n;
|
||||
|
||||
testWidgets('loads German localization and displays tabHome',
|
||||
(tester) async {
|
||||
late AppLocalizations capturedL10n;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: const [Locale('de')],
|
||||
locale: const Locale('de'),
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
capturedL10n = AppLocalizations.of(context);
|
||||
return Text(capturedL10n.tabHome);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify the rendered text contains the expected German string
|
||||
expect(find.text('\u00dcbersicht'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('all critical keys are non-empty', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: const [Locale('de')],
|
||||
locale: const Locale('de'),
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
l10n = AppLocalizations.of(context);
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(l10n.appTitle, isNotEmpty);
|
||||
expect(l10n.tabHome, isNotEmpty);
|
||||
expect(l10n.tabRooms, isNotEmpty);
|
||||
expect(l10n.tabSettings, isNotEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:household_keeper/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user