diff --git a/.github/workflows/design-system-tests.yml b/.github/workflows/design-system-tests.yml new file mode 100644 index 0000000..ea96497 --- /dev/null +++ b/.github/workflows/design-system-tests.yml @@ -0,0 +1,36 @@ +name: design_system tests + +on: + push: + branches: [main] + paths: ['design_system/**'] + pull_request: + branches: [main] + paths: ['design_system/**'] + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: design_system + + steps: + - uses: actions/checkout@v4 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Install dependencies + run: flutter pub get + + - name: Analyze + run: flutter analyze + + - name: Run tests with coverage + run: flutter test --coverage + + - name: Enforce 100% line coverage + run: scripts/check_coverage.sh coverage/lcov.info 100 diff --git a/design_system/lib/src/empty_state.dart b/design_system/lib/src/empty_state.dart index f1df8f4..2847532 100644 --- a/design_system/lib/src/empty_state.dart +++ b/design_system/lib/src/empty_state.dart @@ -14,7 +14,6 @@ class EmptyState extends StatelessWidget { required this.icon, required this.title, required this.message, - this.action, super.key, }); @@ -27,15 +26,14 @@ class EmptyState extends StatelessWidget { /// One line explaining what to do about it. final String message; - /// Optional call to action below the message — usually the button that - /// creates the first item. Omit it where the empty state is a *filter* - /// result rather than an empty collection: there is nothing to create. - final Widget? action; + // Deliberately no `action` slot. No donor had one: home_inventory's three + // call sites are pure placeholders, and kuhylog's action-bearing `_empty` + // is a different element. Adding one here would be speculative surface -- + // the widget nobody imports, one parameter down. @override Widget build(BuildContext context) { final theme = Theme.of(context); - final action = this.action; return Center( child: Padding( padding: const EdgeInsets.all(AppSpacing.xl), @@ -57,10 +55,6 @@ class EmptyState extends StatelessWidget { color: theme.colorScheme.onSurfaceVariant, ), ), - if (action != null) ...[ - const SizedBox(height: AppSpacing.lg), - action, - ], ], ), ), diff --git a/design_system/test/empty_state_test.dart b/design_system/test/empty_state_test.dart index 5b6f2a8..f522674 100644 --- a/design_system/test/empty_state_test.dart +++ b/design_system/test/empty_state_test.dart @@ -2,16 +2,15 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -Future _pumpEmptyState(WidgetTester tester, {Widget? action}) { +Future _pumpEmptyState(WidgetTester tester) { return tester.pumpWidget( MaterialApp( theme: buildDarkTheme(), - home: Scaffold( + home: const Scaffold( body: EmptyState( icon: Icons.inbox_outlined, title: 'Nothing here yet', message: 'Add your first item to get started.', - action: action, ), ), ), @@ -49,17 +48,11 @@ void main() { expect(title.style?.color, isNot(scheme.onSurfaceVariant)); }); - testWidgets('omits the action slot by default', (tester) async { + testWidgets('is a pure placeholder with no action', (tester) async { + // Deliberate: no donor had an action slot, so adding one would be + // speculative surface on a widget that has no consumer yet. await _pumpEmptyState(tester); - expect(find.byType(ElevatedButton), findsNothing); - }); - - testWidgets('renders an action when one is given', (tester) async { - await _pumpEmptyState( - tester, - action: ElevatedButton(onPressed: () {}, child: const Text('Add item')), - ); - expect(find.text('Add item'), findsOneWidget); + expect(find.byType(ButtonStyleButton), findsNothing); }); testWidgets('stays centred and shrink-wrapped', (tester) async {