feat(flutter/library): card + row widgets (artist/album/track)

HorizontalScrollRow takes an optional shared ScrollController so
multi-row sections (Most played: 3 rows) couple their scroll like the
web HorizontalScrollRow.svelte. Cards fall back to the synced
album-fallback.svg when coverUrl is empty.
This commit is contained in:
2026-05-02 17:29:03 -04:00
parent 3e267918b7
commit c47deb3c87
5 changed files with 253 additions and 0 deletions
@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:minstrel/library/widgets/album_card.dart';
import 'package:minstrel/library/widgets/track_row.dart';
import 'package:minstrel/models/album.dart';
import 'package:minstrel/models/track.dart';
import 'package:minstrel/theme/theme_data.dart';
void main() {
testWidgets('AlbumCard renders title and artist', (tester) async {
await tester.pumpWidget(MaterialApp(
theme: buildThemeData(),
home: Scaffold(
body: AlbumCard(
album: const AlbumRef(
id: 'a',
title: 'Geogaddi',
artistId: 'x',
artistName: 'Boards of Canada',
),
onTap: () {},
),
),
));
expect(find.text('Geogaddi'), findsOneWidget);
expect(find.text('Boards of Canada'), findsOneWidget);
});
testWidgets('TrackRow shows mm:ss duration', (tester) async {
await tester.pumpWidget(MaterialApp(
theme: buildThemeData(),
home: Scaffold(
body: TrackRow(
track: const TrackRef(
id: 't',
title: 'Roygbiv',
albumId: 'a',
artistId: 'x',
durationSec: 137,
trackNumber: 4,
),
onTap: () {},
),
),
));
expect(find.text('02:17'), findsOneWidget);
});
}