mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 22:47:14 +01:00
unified-design-system/ froze the token layer as prose -- 1220 lines of README, tokens.md and annotated HTML, and zero importable code. Every Flutter repo transcribed the token table by hand, so the "shared" palette was enforced only by discipline, and had already drifted: 10 divergent theme.dart copies (todo and untools byte-identical, the other 8 each unique), ~318 raw color literals, 4 hand-rolled AppStatusColors extensions with inconsistent field sets, ~35 inlined AlertDialogs, ~38 raw SnackBar sites with no helper anywhere. This promotes the token layer into an importable component layer, so the design system is enforced by the compiler and the package manager instead. The components own the tokens: a consumer has nothing local left to edit. Scales are the six-repo consensus, which is also what tokens.md specifies; drifted repos get corrected to this rather than the reverse. Themes are built from explicit ColorSchemes, never fromSeed -- the palette is hand-picked for contrast, and secondary/tertiary are set explicitly so SegmentedButton cannot fall back to stock Material teal. Widgets are extracted only where two or more repos already had a structurally similar implementation; SectionHeader reconciles untools' trailing-action variant with billsplit's padded one as an opt-in union, so either caller can adopt it with no visual change. 63 tests, 102/102 lines covered, gated at 100% on pre-push. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XS1m7b1piBtAL22USLW6jL
114 lines
3.4 KiB
Dart
114 lines
3.4 KiB
Dart
import 'package:design_system/design_system.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
/// Pumps a screen whose button calls [onPressed] with a context that has a
|
|
/// `ScaffoldMessenger` above it.
|
|
Future<void> _pumpMessengerScreen(
|
|
WidgetTester tester,
|
|
void Function(BuildContext context) onPressed,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: buildDarkTheme(),
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder: (context) => ElevatedButton(
|
|
onPressed: () => onPressed(context),
|
|
child: const Text('go'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('showToast displays the message', (tester) async {
|
|
await _pumpMessengerScreen(tester, (context) {
|
|
showToast(context, 'Saved');
|
|
});
|
|
await tester.tap(find.text('go'));
|
|
await tester.pump();
|
|
expect(find.text('Saved'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('showToast leaves the background unstyled', (tester) async {
|
|
await _pumpMessengerScreen(tester, (context) {
|
|
showToast(context, 'Saved');
|
|
});
|
|
await tester.tap(find.text('go'));
|
|
await tester.pump();
|
|
final snack = tester.widget<SnackBar>(find.byType(SnackBar));
|
|
expect(snack.backgroundColor, isNull);
|
|
expect(snack.duration, const Duration(seconds: 3));
|
|
});
|
|
|
|
testWidgets('showError uses the danger hue and stays up longer', (
|
|
tester,
|
|
) async {
|
|
await _pumpMessengerScreen(tester, (context) {
|
|
showError(context, 'Sync failed');
|
|
});
|
|
await tester.tap(find.text('go'));
|
|
await tester.pump();
|
|
|
|
final snack = tester.widget<SnackBar>(find.byType(SnackBar));
|
|
expect(snack.backgroundColor, AppPalette.danger);
|
|
expect(snack.duration, const Duration(seconds: 5));
|
|
|
|
final text = tester.widget<Text>(find.text('Sync failed'));
|
|
expect(text.style?.color, AppPalette.onFill);
|
|
});
|
|
|
|
testWidgets('both float and use the shared radius', (tester) async {
|
|
await _pumpMessengerScreen(tester, (context) {
|
|
showToast(context, 'Saved');
|
|
});
|
|
await tester.tap(find.text('go'));
|
|
await tester.pump();
|
|
|
|
final snack = tester.widget<SnackBar>(find.byType(SnackBar));
|
|
expect(snack.behavior, SnackBarBehavior.floating);
|
|
expect(
|
|
(snack.shape! as RoundedRectangleBorder).borderRadius,
|
|
BorderRadius.circular(AppRadius.sm),
|
|
);
|
|
});
|
|
|
|
testWidgets('a second message replaces the first rather than queueing', (
|
|
tester,
|
|
) async {
|
|
await _pumpMessengerScreen(tester, (context) {
|
|
showToast(context, 'First');
|
|
showToast(context, 'Second');
|
|
});
|
|
await tester.tap(find.text('go'));
|
|
await tester.pump();
|
|
|
|
expect(find.text('First'), findsNothing);
|
|
expect(find.text('Second'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('returns null instead of throwing with no messenger above', (
|
|
tester,
|
|
) async {
|
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? toast;
|
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? error;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Builder(
|
|
builder: (context) {
|
|
toast = showToast(context, 'Saved');
|
|
error = showError(context, 'Broke');
|
|
return const SizedBox.shrink();
|
|
},
|
|
),
|
|
),
|
|
);
|
|
expect(toast, isNull);
|
|
expect(error, isNull);
|
|
});
|
|
}
|