feat: alphabet list, change Layout etc

This commit is contained in:
2026-10-11 20:37:59 +02:00
parent 489896305d
commit 072f560989
31 changed files with 1847 additions and 832 deletions
+143
View File
@@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kooltap/l10n/app_localizations.dart';
import 'package:kooltap/models/bar_tab.dart';
import 'package:kooltap/utils/name_index.dart';
import 'package:kooltap/views/bar/tab_panel.dart';
const _names = [
'Zoë', 'anna', 'Émile', 'Bram', 'Mieke', 'Marc', 'Ward', 'Tom', 'Sofie', //
'Lotte', 'Koen', 'Jan', 'Inge', 'Hanne', 'Greet', 'Frank', 'Dirk', 'Chris',
'Nele', 'Olaf', 'Pieter', 'Rik', 'Vera', 'Yves', '2de tafel', 'Eva',
];
void main() {
group('name index', () {
test('folds case and accents into the index letter', () {
expect(indexLetterOf('émile'), 'E');
expect(indexLetterOf('Zoë'), 'Z');
expect(indexLetterOf(' anna'), 'A');
expect(indexLetterOf('2de tafel'), '#');
expect(indexLetterOf(''), '#');
});
test('sorts A–Z regardless of case and accents, # last', () {
final sorted = ['Zoë', 'émile', 'Eva', 'anna', '2de tafel', 'Bram']
..sort(compareByIndex);
expect(sorted, ['anna', 'Bram', 'émile', 'Eva', 'Zoë', '2de tafel']);
});
});
group('TabPanel letter index', () {
final tabs = [
for (final (index, name) in _names.indexed)
BarTab(
id: 'tab-$index',
customerName: name,
openedAt: DateTime(2026, 10, 11, 12, index),
items: const [],
),
];
Future<void> pumpPanel(
WidgetTester tester, {
bool showSearch = false,
}) async {
tester.view.physicalSize = const Size(500, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: TabPanel(
tabs: tabs,
selectedTab: null,
selectedTabId: null,
stockByProductId: const {},
showSearch: showSearch,
onTabSelected: (_) {},
onItemQuantityChanged: (_, _) async {},
onCloseTabPressed: () {},
onTabEdited: (_) async {},
onTabDeleted: (_) async {},
),
),
),
);
}
String firstVisibleName(WidgetTester tester) {
final list = tester.getRect(find.byType(OpenTabsList));
final visible = _names.where((name) {
final finder = find.text(name);
if (finder.evaluate().isEmpty) return false;
final rect = tester.getRect(finder);
return rect.top >= list.top && rect.bottom <= list.bottom;
}).toList();
return visible.reduce(
(a, b) =>
tester.getRect(find.text(a)).top < tester.getRect(find.text(b)).top
? a
: b,
);
}
Future<void> tapLetter(WidgetTester tester, String letter) async {
await tester.tap(
find.descendant(
of: find.byWidgetPredicate(
(widget) => widget.runtimeType.toString() == '_LetterIndexBar',
),
matching: find.text(letter),
),
);
await tester.pumpAndSettle();
}
testWidgets('lists the tabs alphabetically', (tester) async {
await pumpPanel(tester);
expect(firstVisibleName(tester), 'anna');
});
testWidgets('tapping a letter scrolls to its first name', (tester) async {
await pumpPanel(tester);
await tapLetter(tester, 'M');
expect(firstVisibleName(tester), 'Marc');
await tapLetter(tester, 'E');
expect(firstVisibleName(tester), 'Émile');
});
testWidgets('shows the search bar only when asked', (tester) async {
await pumpPanel(tester);
expect(find.byType(TextField), findsNothing);
await pumpPanel(tester, showSearch: true);
expect(find.byType(TextField), findsOneWidget);
});
testWidgets('closing the search clears the filter', (tester) async {
await pumpPanel(tester, showSearch: true);
await tester.enterText(find.byType(TextField), 'Marc');
await tester.pump();
expect(find.text('anna'), findsNothing);
await pumpPanel(tester);
expect(find.text('anna'), findsOneWidget);
});
testWidgets('a letter without names goes to the next one', (tester) async {
await pumpPanel(tester);
await tapLetter(tester, 'Q');
expect(firstVisibleName(tester), 'Rik');
});
});
}