Halkyon's shared packages
A pub workspace of the Dart packages Halkyon's apps share: halkyon_ui, Halkyon's look (the theme, fonts, layout and shared widgets, with texts in English and German), halkyon_core's low-level primitives and halkyon_interop's cross-app contracts. Dart 3.13 with LeanCode's lints, configured once at the root; apps take a package by git, pinned to a signed commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
commit
80fa7b9628
1 changed files with 6762 additions and 0 deletions
216
packages/halkyon_ui/test/widget/adaptive_sheet_test.dart
Normal file
216
packages/halkyon_ui/test/widget/adaptive_sheet_test.dart
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_ui/halkyon_ui.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
void main() {
|
||||
/// Opens a sheet titled "New studyset" with [body] and a Create button, in
|
||||
/// a window [width] wide, with a status bar [statusBar] high on top.
|
||||
Future<void> open(
|
||||
WidgetTester tester, {
|
||||
required double width,
|
||||
Widget body = const Text('Body'),
|
||||
double statusBar = 0,
|
||||
bool busy = false,
|
||||
}) async {
|
||||
tester.view.physicalSize = Size(width, 900);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.padding = FakeViewPadding(top: statusBar);
|
||||
tester.view.viewPadding = FakeViewPadding(top: statusBar);
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: HalkyonUiLocalizations.localizationsDelegates,
|
||||
supportedLocales: HalkyonUiLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => TextButton(
|
||||
onPressed: () => showAdaptiveSheet<void>(
|
||||
context,
|
||||
swipeable: !busy,
|
||||
builder: (context) => SheetScaffold(
|
||||
title: 'New studyset',
|
||||
busy: busy,
|
||||
body: body,
|
||||
footer: FilledButton(
|
||||
onPressed: () {},
|
||||
child: const Text('Create'),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text('Open'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.text('Open'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('is a bottom sheet below expanded windows', (tester) async {
|
||||
await open(tester, width: 839);
|
||||
|
||||
expect(find.byType(BottomSheet), findsOneWidget);
|
||||
expect(find.byType(Dialog), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('is a dialog from expanded windows on', (tester) async {
|
||||
await open(tester, width: 840);
|
||||
|
||||
expect(find.byType(Dialog), findsOneWidget);
|
||||
expect(find.byType(BottomSheet), findsNothing);
|
||||
});
|
||||
|
||||
for (final width in [400.0, 1200.0]) {
|
||||
testWidgets('keeps its close button and footer in view above a long body '
|
||||
'(${width.round()} wide)', (tester) async {
|
||||
await open(
|
||||
tester,
|
||||
width: width,
|
||||
body: Column(
|
||||
children: [for (var i = 0; i < 80; i++) Text('Line $i')],
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byTooltip('Close').hitTestable(), findsOneWidget);
|
||||
expect(find.text('Create').hitTestable(), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('stays below the status bar with the keyboard open', (
|
||||
tester,
|
||||
) async {
|
||||
await open(
|
||||
tester,
|
||||
width: 400,
|
||||
statusBar: 48,
|
||||
body: Column(
|
||||
children: [for (var i = 0; i < 80; i++) Text('Line $i')],
|
||||
),
|
||||
);
|
||||
|
||||
tester.view.viewInsets = const FakeViewPadding(bottom: 400);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.getTopLeft(find.byType(BottomSheet)).dy, 48);
|
||||
expect(find.byTooltip('Close').hitTestable(), findsOneWidget);
|
||||
expect(find.text('Create').hitTestable(), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('closes with its close button', (tester) async {
|
||||
await open(tester, width: 400);
|
||||
|
||||
await tester.tap(find.byTooltip('Close'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('New studyset'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('while busy, its close button is off and back keeps it open', (
|
||||
tester,
|
||||
) async {
|
||||
await open(tester, width: 400, busy: true);
|
||||
|
||||
expect(
|
||||
tester
|
||||
.widget<IconButton>(
|
||||
find.ancestor(
|
||||
of: find.byTooltip('Close'),
|
||||
matching: find.byType(IconButton),
|
||||
),
|
||||
)
|
||||
.onPressed,
|
||||
isNull,
|
||||
);
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('New studyset'), findsOneWidget);
|
||||
});
|
||||
|
||||
group('a body longer than the sheet', () {
|
||||
final tall = Column(
|
||||
children: [for (var i = 0; i < 40; i++) const Text('Line')],
|
||||
);
|
||||
Finder below() => find.byKey(SheetScaffold.moreBelowKey);
|
||||
Finder above() => find.byKey(SheetScaffold.moreAboveKey);
|
||||
|
||||
testWidgets('shows a line above the buttons while more lies below, and '
|
||||
'one under the title while more lies above', (tester) async {
|
||||
// As a dialog: a modal bottom sheet takes a test's drag itself.
|
||||
await open(tester, width: 1000, body: tall);
|
||||
|
||||
expect(below(), findsOneWidget);
|
||||
expect(above(), findsNothing);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(SingleChildScrollView),
|
||||
const Offset(0, -300),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(below(), findsOneWidget);
|
||||
expect(above(), findsOneWidget);
|
||||
expect(
|
||||
tester
|
||||
.state<ScrollableState>(
|
||||
find.descendant(
|
||||
of: find.byType(SingleChildScrollView),
|
||||
matching: find.byType(Scrollable),
|
||||
),
|
||||
)
|
||||
.position
|
||||
.pixels,
|
||||
greaterThan(0),
|
||||
reason: 'the line coming keeps where the body was scrolled to',
|
||||
);
|
||||
|
||||
await tester.drag(
|
||||
find.byType(SingleChildScrollView),
|
||||
const Offset(0, -5000),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(below(), findsNothing);
|
||||
expect(above(), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('has a scrollbar, always shown on the desktop', (tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.linux;
|
||||
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
||||
await open(tester, width: 1000, body: tall);
|
||||
|
||||
final scrollbar = tester.widget<Scrollbar>(find.byType(Scrollbar));
|
||||
expect(scrollbar.thumbVisibility, isTrue);
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('a body that fits shows no lines', (tester) async {
|
||||
await open(tester, width: 400);
|
||||
|
||||
expect(find.byKey(SheetScaffold.moreBelowKey), findsNothing);
|
||||
expect(find.byKey(SheetScaffold.moreAboveKey), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets("the body keeps clear of the scroll view's edges, where it is "
|
||||
'cut and the scrollbar runs', (tester) async {
|
||||
for (final width in [400.0, 1000.0]) {
|
||||
await open(
|
||||
tester,
|
||||
width: width,
|
||||
body: const TextField(
|
||||
decoration: InputDecoration(border: OutlineInputBorder()),
|
||||
),
|
||||
);
|
||||
|
||||
final scrolling = tester.getRect(find.byType(SingleChildScrollView));
|
||||
final field = tester.getRect(find.byType(TextField));
|
||||
expect(field.left - scrolling.left, greaterThanOrEqualTo(16));
|
||||
expect(scrolling.right - field.right, greaterThanOrEqualTo(16));
|
||||
// The title and the button stay in line with the body.
|
||||
expect(tester.getTopLeft(find.text('New studyset')).dx, field.left);
|
||||
expect(tester.getTopRight(find.byType(FilledButton)).dx, field.right);
|
||||
await tester.tap(find.byTooltip('Close'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in a new issue