Bug (Code-Review, verifiziert): _Angaben (Titel/Künstler/Fortschritt/ Controls) hat eine intrinsische Mindesthöhe, die während der Öffnen-Geste (progress zwischen ~0.3 und ~1.0, siehe main.dart Positioned.fromRect mit Rect.lerp Mini-/Vollbild-Player) das noch kleinere, interpolierte Rechteck übersteigt — Flutter meldete dann "RenderFlex overflowed" bei praktisch jeder normalen Öffnen-Geste. Fix: der Inhalt-Slot (in beiden responsiven Zweigen — Row nebeneinander und Column untereinander) steckt jetzt in ClipRect(child: ConstrainedBox(maxHeight: raum.maxHeight - Lücke, child: OverflowBox(maxHeight: infinity, fit: deferToChild, child: _Angaben))). ConstrainedBox deckelt nur, was OverflowBox nach AUSSEN als eigene Größe meldet (verhindert, dass der umgebende Row/Column selbst überläuft); OverflowBox gibt _Angaben davon unabhängig weiterhin unbegrenzte Höhe (verhindert, dass _Angaben intern überläuft) — überschüssiger Inhalt wird vom ClipRect während der kurzen, ohnehin noch recht durchsichtigen Übergangsphase einfach nicht gemalt statt eine Overflow-Warnung auszulösen. Bei ausreichend Platz (Endzustand progress==1) ist raum.maxHeight groß genug, dass nichts geklemmt wird — die Optik bleibt dort unverändert (now_playing_heart_test.dart bestätigt das weiterhin). Test zuerst (RED: "RenderFlex overflowed by 13 pixels" reproduziert, mit NowPlayingScreen in einem echten, interpolierten Zwischenrechteck bei progress≈0.4 auf Basis der HomeShell-Rect-Formel): test/player/ now_playing_screen_overflow_test.dart. Volle Suite (626 Tests) grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SM6e73V9ZethPiqHSocxUA
129 lines
4.9 KiB
Dart
129 lines
4.9 KiB
Dart
import 'package:audio_service/audio_service.dart';
|
|
import 'package:drift/drift.dart' show driftRuntimeOptions;
|
|
import 'package:drift/native.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:melo/library/category_service.dart';
|
|
import 'package:melo/library/database.dart';
|
|
import 'package:melo/library/playlist_service.dart';
|
|
import 'package:melo/player/audio_handler.dart';
|
|
import 'package:melo/player/mini_player.dart';
|
|
import 'package:melo/player/now_playing_screen.dart';
|
|
import 'package:melo/player/player_expansion_controller.dart';
|
|
import 'package:melo/settings/app_settings.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
/// Ersetzt in [MeloAudioHandler.loadPlaylist] den echten just_audio-Zugriff —
|
|
/// dieselbe Klasse wie in now_playing_heart_test.dart.
|
|
class _FakeAudioHandler extends MeloAudioHandler {
|
|
_FakeAudioHandler(MeloDb db) : super(db: db);
|
|
|
|
@override
|
|
Future<int> loadPlaylist(List<MediaItem> items, {int startIndex = 0}) async {
|
|
queue.add(items);
|
|
if (items.isNotEmpty) mediaItem.add(items[startIndex]);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
void stelleKanaeleRuhig() {
|
|
final messenger =
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
|
messenger.setMockMethodCallHandler(
|
|
const MethodChannel('plugins.flutter.io/path_provider'),
|
|
(call) async => '/tmp/melo-test',
|
|
);
|
|
messenger.setMockMethodCallHandler(
|
|
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
|
(call) async => null,
|
|
);
|
|
}
|
|
|
|
setUp(() => SharedPreferences.setMockInitialValues({}));
|
|
|
|
testWidgets(
|
|
'NowPlayingScreen in einem während der Öffnen-Geste noch kleinen, '
|
|
'interpolierten Rechteck (progress ~0.4) wirft keinen '
|
|
'RenderFlex-Overflow', (tester) async {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
|
stelleKanaeleRuhig();
|
|
final db = MeloDb(NativeDatabase.memory());
|
|
final kategorien = CategoryService(db);
|
|
final playlists = PlaylistService(db);
|
|
final einstellungen = AppSettings();
|
|
await einstellungen.init();
|
|
final handler = _FakeAudioHandler(db);
|
|
addTearDown(kategorien.dispose);
|
|
addTearDown(playlists.dispose);
|
|
|
|
// Ein echtes MediaItem, wie es beim Abspielen im mediaItem-Stream steht
|
|
// — NowPlayingScreen zeigt sonst nur "Nichts in Wiedergabe" und hat dann
|
|
// gar keinen Inhalt, der überlaufen könnte.
|
|
handler.mediaItem.add(const MediaItem(
|
|
id: 'song-1',
|
|
title: 'Roses',
|
|
artist: 'Neoni',
|
|
));
|
|
|
|
// Dieselbe Rect-Interpolation wie HomeShell._miniRect/_vollbildRect bei
|
|
// progress=0.4 auf der Standard-Testbildschirmgröße (800x600, siehe
|
|
// flutter_test-Default) — main.dart positioniert NowPlayingScreen
|
|
// während der Öffnen-Geste über genau so ein Positioned.fromRect mit
|
|
// Rect.lerp zwischen Mini- und Vollbild-Rechteck. Bei p>0.3 blendet der
|
|
// Inhalt laut Spec schon sichtbar ein (main.dart: Opacity((p-0.3)/0.7)),
|
|
// das Rechteck ist zu dem Zeitpunkt aber noch weit von der
|
|
// Vollbildgröße entfernt.
|
|
const p = 0.4;
|
|
const screenSize = Size(800, 600);
|
|
const miniHoehe = MiniPlayer.hoehe;
|
|
final unten = screenSize.height - miniHoehe - kBottomNavigationBarHeight;
|
|
final miniRect = Rect.fromLTWH(0, unten, screenSize.width, miniHoehe);
|
|
final vollbildRect =
|
|
Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
|
|
final rect = Rect.lerp(miniRect, vollbildRect, p)!;
|
|
|
|
final expansion = PlayerExpansionController(vsync: tester);
|
|
addTearDown(expansion.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MultiProvider(
|
|
providers: [
|
|
Provider<MeloDb>.value(value: db),
|
|
Provider<MeloAudioHandler>.value(value: handler),
|
|
ChangeNotifierProvider<CategoryService>.value(value: kategorien),
|
|
ChangeNotifierProvider<PlaylistService>.value(value: playlists),
|
|
ChangeNotifierProvider<AppSettings>.value(value: einstellungen),
|
|
ChangeNotifierProvider<PlayerExpansionController>.value(
|
|
value: expansion),
|
|
],
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Stack(
|
|
children: [
|
|
Positioned.fromRect(
|
|
rect: rect,
|
|
child: const NowPlayingScreen(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// Aufräum-Reihenfolge wie in den übrigen Tests dieser Datei-Familie:
|
|
// erst den Baum abbauen, dann den Handler disposen (offener Positions-
|
|
// Timer, siehe now_playing_heart_test.dart).
|
|
await tester.pumpWidget(const SizedBox());
|
|
handler.dispose();
|
|
await tester.pump(const Duration(milliseconds: 1));
|
|
});
|
|
}
|