test(flutter): fix two latent failures exposed by un-skipping the drift cohort

Both fixes paired with the ci-flutter:3.44 rebuild that adds
libsqlite3-dev to the runner image (CI-runner push #2: libsqlite3-0
→ -dev because dart:ffi opens the unversioned .so symlink that only
the dev package ships).

widgets_smoke_test (TrackRow):
  TrackRow contains CachedIndicator which reaches
  audioCacheManagerProvider → appDbProvider → drift_flutter's
  `driftDatabase()`. That schedules a deferred-init Timer that
  outlives the test widget tree and trips the
  "A Timer is still pending after dispose" invariant. Override
  appDbProvider in the test to use AppDb(NativeDatabase.memory())
  directly — bypasses drift_flutter's Timer-using init path, still
  exercises real SQLite via FFI.

like_button_test (tap toggles + rollback):
  LikeButton was migrated to LucideHeart (SVG widget with `filled`
  bool) in the Lucide sweep; the test's `find.byIcon(Icons.favorite)`
  is stale. Replace with a small heartFilled() helper that reads
  the LucideHeart's `filled` prop straight off the widget tree.
  Same assertion semantics, just against the post-migration shape.

The four sync_controller failures need no code change — they're
the same root cause as the drift-tagged cohort (libsqlite3.so
missing → try/catch returns null → `result?.upserts` is null
instead of the expected 0). The image fix should clear them.

Fable #399 / local #62.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 18:15:18 -04:00
parent 85183c455a
commit 8fb6b4fbb3
2 changed files with 26 additions and 5 deletions
@@ -1,7 +1,10 @@
import 'package:drift/native.dart' show NativeDatabase;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:minstrel/cache/audio_cache_manager.dart' show appDbProvider;
import 'package:minstrel/cache/db.dart';
import 'package:minstrel/library/widgets/album_card.dart';
import 'package:minstrel/library/widgets/track_row.dart';
import 'package:minstrel/models/album.dart';
@@ -29,9 +32,20 @@ void main() {
});
testWidgets('TrackRow shows mm:ss duration', (tester) async {
// TrackRow now contains CachedIndicator (ConsumerWidget) so a
// ProviderScope is required at the root.
// TrackRow contains CachedIndicator (ConsumerWidget) which reaches
// audioCacheManagerProvider → appDbProvider. Override appDbProvider
// with an explicit NativeDatabase.memory() executor — drift_flutter's
// default `driftDatabase()` schedules a deferred-init Timer that
// outlives the test widget tree and trips
// _verifyInvariants("A Timer is still pending after dispose").
await tester.pumpWidget(ProviderScope(
overrides: [
appDbProvider.overrideWith((ref) {
final db = AppDb(NativeDatabase.memory());
ref.onDispose(db.close);
return db;
}),
],
child: MaterialApp(
theme: buildThemeData(),
home: Scaffold(
@@ -9,6 +9,7 @@ import 'package:minstrel/models/album.dart';
import 'package:minstrel/models/artist.dart';
import 'package:minstrel/models/page.dart';
import 'package:minstrel/models/track.dart';
import 'package:minstrel/shared/widgets/lucide_heart.dart';
import 'package:minstrel/theme/theme_data.dart';
class _ThrowingLikesApi implements LikesApi {
@@ -62,10 +63,16 @@ void main() {
));
await tester.pumpAndSettle();
// First tap = like; succeeds → icon flips to favorite.
// Helper: read the current heart-fill state straight off the widget.
// LikeButton renders LucideHeart(filled: <liked>), so the assertion
// is "the LucideHeart in the tree is filled" not "find an Icon."
bool heartFilled() =>
tester.widget<LucideHeart>(find.byType(LucideHeart)).filled;
// First tap = like; succeeds → heart flips to filled.
await tester.tap(find.byType(IconButton));
await tester.pump();
expect(find.byIcon(Icons.favorite), findsOneWidget);
expect(heartFilled(), isTrue);
// Force the next mutation to fail; rollback restores prior state.
api.throwOnNext = true;
@@ -74,6 +81,6 @@ void main() {
await controller.toggle(LikeKind.album, 'al-1');
} catch (_) {/* expected */}
await tester.pump();
expect(find.byIcon(Icons.favorite), findsOneWidget); // rolled back
expect(heartFilled(), isTrue); // rolled back to liked
});
}