initial commit
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:manager/core/theme/app_theme.dart';
|
||||
import 'package:manager/core/widgets/data_table_scaffold.dart';
|
||||
import 'package:manager/core/widgets/empty_state.dart';
|
||||
|
||||
void main() {
|
||||
const columns = [DataColumn(label: Text('Name')), DataColumn(label: Text('Status'))];
|
||||
|
||||
testWidgets('renders the given rows', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(
|
||||
body: DataTableScaffold(
|
||||
columns: columns,
|
||||
rows: const [
|
||||
DataRow(cells: [DataCell(Text('postgres')), DataCell(Text('up'))]),
|
||||
DataRow(cells: [DataCell(Text('grafana')), DataCell(Text('up'))]),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('postgres'), findsOneWidget);
|
||||
expect(find.text('grafana'), findsOneWidget);
|
||||
expect(find.byType(DataTable), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('shows the empty-state fallback instead of an empty table', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(
|
||||
body: DataTableScaffold(
|
||||
columns: columns,
|
||||
rows: const [],
|
||||
empty: const EmptyState(icon: Icons.layers_outlined, title: 'No services yet'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('No services yet'), findsOneWidget);
|
||||
expect(find.byType(DataTable), findsNothing);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:manager/core/theme/app_theme.dart';
|
||||
import 'package:manager/core/widgets/empty_state.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('renders title/message and invokes the action callback', (tester) async {
|
||||
var tapped = false;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(
|
||||
body: EmptyState(
|
||||
icon: Icons.layers_outlined,
|
||||
title: 'No services yet',
|
||||
message: 'Scan a compose folder to discover services.',
|
||||
actionLabel: 'Scan now',
|
||||
onAction: () => tapped = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('No services yet'), findsOneWidget);
|
||||
expect(find.text('Scan a compose folder to discover services.'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.widgetWithText(OutlinedButton, 'Scan now'));
|
||||
await tester.pump();
|
||||
expect(tapped, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('omits the action button when none is given', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: const Scaffold(
|
||||
body: EmptyState(icon: Icons.inbox_outlined, title: 'Nothing here'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(OutlinedButton), findsNothing);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:manager/core/theme/app_theme.dart';
|
||||
import 'package:manager/core/widgets/error_banner.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('renders the message and invokes onRetry', (tester) async {
|
||||
var retried = false;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(
|
||||
body: ErrorBanner(
|
||||
message: "Can't reach this node.",
|
||||
onRetry: () => retried = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text("Can't reach this node."), findsOneWidget);
|
||||
await tester.tap(find.text('Retry'));
|
||||
await tester.pump();
|
||||
expect(retried, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('omits the retry button when onRetry is null', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: const Scaffold(body: ErrorBanner(message: 'Something broke.')),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Retry'), findsNothing);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:manager/core/theme/app_theme.dart';
|
||||
import 'package:manager/core/widgets/mono_text.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('renders in the JetBrains Mono family, small variant uses a smaller size', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: const Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
MonoText('/opt/compose/postgres/docker-compose.yml'),
|
||||
MonoText('svc_123', small: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final regular = tester.widget<Text>(find.text('/opt/compose/postgres/docker-compose.yml'));
|
||||
final small = tester.widget<Text>(find.text('svc_123'));
|
||||
|
||||
expect(regular.style?.fontFamily, 'JetBrains Mono');
|
||||
expect(small.style?.fontFamily, 'JetBrains Mono');
|
||||
expect(small.style!.fontSize!, lessThan(regular.style!.fontSize!));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:manager/core/theme/app_theme.dart';
|
||||
import 'package:manager/core/widgets/slide_over_panel.dart';
|
||||
|
||||
void main() {
|
||||
Widget wrap({required bool open, required VoidCallback onClose}) {
|
||||
return MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
const Center(child: Text('List content')),
|
||||
SlideOverPanel(
|
||||
open: open,
|
||||
onClose: onClose,
|
||||
title: 'vaultwarden',
|
||||
body: const Text('Detail body'),
|
||||
actions: [OutlinedButton(onPressed: () {}, child: const Text('Stop'))],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('closed panel ignores taps on its (invisible) close button', (tester) async {
|
||||
var closed = false;
|
||||
await tester.pumpWidget(wrap(open: false, onClose: () => closed = true));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// IgnorePointer(ignoring: true) means the close icon exists in the tree
|
||||
// but isn't hit-testable — tapping it must not fire onClose.
|
||||
await tester.tap(find.byIcon(Icons.close), warnIfMissed: false);
|
||||
await tester.pump();
|
||||
expect(closed, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('open panel responds to the close button and the scrim tap', (tester) async {
|
||||
var closed = false;
|
||||
await tester.pumpWidget(wrap(open: true, onClose: () => closed = true));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('vaultwarden'), findsOneWidget);
|
||||
expect(find.text('Detail body'), findsOneWidget);
|
||||
expect(find.text('Stop'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.close));
|
||||
expect(closed, isTrue);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:manager/core/theme/app_theme.dart';
|
||||
import 'package:manager/core/widgets/sparkline.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('paints without error for multiple points, one point, and empty data', (tester) async {
|
||||
for (final values in [
|
||||
[12.4, 16.0, 4.1, 19.8, 9.2],
|
||||
[12.4],
|
||||
<double>[],
|
||||
]) {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(body: Center(child: Sparkline(values: values))),
|
||||
),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
expect(find.byType(CustomPaint), findsWidgets);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:manager/core/theme/app_theme.dart';
|
||||
import 'package:manager/core/theme/status_colors.dart';
|
||||
import 'package:manager/core/widgets/stat_tile.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('renders label, value, qualifier and caption', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: const Scaffold(
|
||||
body: StatTile(
|
||||
label: 'Services',
|
||||
value: '7',
|
||||
valueQualifier: '/ 9 up',
|
||||
caption: '1 down · 1 unknown',
|
||||
severity: Severity.critical,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Services'), findsOneWidget);
|
||||
expect(find.textContaining('7'), findsOneWidget);
|
||||
expect(find.textContaining('/ 9 up'), findsOneWidget);
|
||||
expect(find.text('1 down · 1 unknown'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('severity stripe color matches the resolved status color', (tester) async {
|
||||
final key = GlobalKey();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(
|
||||
body: StatTile(key: key, label: 'X', value: '1', severity: Severity.critical),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final context = key.currentContext!;
|
||||
final statusColors = Theme.of(context).extension<StatusColors>()!;
|
||||
|
||||
final stripe = tester.widgetList<Container>(find.byType(Container)).firstWhere(
|
||||
(c) => c.color == statusColors.critical,
|
||||
orElse: () => throw StateError('No stripe container with critical color found'),
|
||||
);
|
||||
expect(stripe.color, statusColors.critical);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:manager/core/theme/app_theme.dart';
|
||||
import 'package:manager/core/theme/status_colors.dart';
|
||||
import 'package:manager/core/widgets/status_pill.dart';
|
||||
|
||||
Widget _wrap(Widget child) => MaterialApp(theme: AppTheme.light(), home: Scaffold(body: Center(child: child)));
|
||||
|
||||
void main() {
|
||||
testWidgets('renders its label', (tester) async {
|
||||
await tester.pumpWidget(_wrap(const StatusPill(label: 'up', severity: Severity.good)));
|
||||
expect(find.text('up'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('good/critical/neutral each resolve to a distinct pill color', (tester) async {
|
||||
Future<Color> pillColorFor(Severity severity) async {
|
||||
await tester.pumpWidget(_wrap(StatusPill(label: severity.name, severity: severity)));
|
||||
final container = tester.widget<Container>(
|
||||
find.descendant(of: find.byType(StatusPill), matching: find.byType(Container)).first,
|
||||
);
|
||||
final decoration = container.decoration as BoxDecoration;
|
||||
return decoration.color!;
|
||||
}
|
||||
|
||||
final good = await pillColorFor(Severity.good);
|
||||
final critical = await pillColorFor(Severity.critical);
|
||||
final neutral = await pillColorFor(Severity.neutral);
|
||||
|
||||
expect(good, isNot(equals(critical)));
|
||||
expect(good, isNot(equals(neutral)));
|
||||
expect(critical, isNot(equals(neutral)));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user