// AC6 regression: button is hidden when opt-in OFF, but visible-but-disabled // with a Tooltip when opt-in ON and model is not ready. // // The QA reject (2026-06-12) caught that the prior implementation rendered // `SizedBox.shrink()` in BOTH cases, eliminating discoverability for opted-in // users still downloading. import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:life_helper/data/ai/llm_service.dart'; import 'package:life_helper/data/ai/model_lifecycle.dart'; import 'package:life_helper/data/db/app_database.dart'; import 'package:life_helper/state/ai_providers.dart'; import 'package:life_helper/state/providers.dart'; import 'package:life_helper/ui/screens/habit_create_screen.dart'; Future _memDb() async { return AppDatabase(NativeDatabase.memory()); } ProviderContainer _container({ required AppDatabase db, required bool optIn, required ModelAvailability availability, }) { return ProviderContainer(overrides: [ appDatabaseProvider.overrideWithValue(db), llmServiceProvider.overrideWithValue(MockLlmService()), aiSettingsProvider.overrideWith((_) async => optIn), modelAvailabilityProvider.overrideWith((_) async => availability), ]); } Widget _wrap(ProviderContainer container) { return UncontrolledProviderScope( container: container, child: const MaterialApp(home: Scaffold(body: HabitCreateScreen())), ); } void main() { testWidgets('AC6: optIn=false → button hidden entirely', (tester) async { final db = await _memDb(); addTearDown(db.close); final c = _container( db: db, optIn: false, availability: ModelAvailability.ready, ); addTearDown(c.dispose); await tester.pumpWidget(_wrap(c)); await tester.pumpAndSettle(); expect(find.text('AI 제안'), findsNothing); expect(find.byTooltip('AI 도움을 먼저 켜주세요'), findsNothing); }); testWidgets( 'AC6: optIn=true, availability=missing → button visible, disabled, tooltip', (tester) async { final db = await _memDb(); addTearDown(db.close); final c = _container( db: db, optIn: true, availability: ModelAvailability.missing, ); addTearDown(c.dispose); await tester.pumpWidget(_wrap(c)); await tester.pumpAndSettle(); // Visible expect(find.text('AI 제안'), findsOneWidget); // Wrapped in tooltip with the AC6 exact message expect(find.byTooltip('AI 도움을 먼저 켜주세요'), findsOneWidget); // Disabled final TextButton btn = tester.widget(find.byType(TextButton).first); expect(btn.onPressed, isNull); }); testWidgets( 'AC6: optIn=true, availability=downloading → button visible+disabled', (tester) async { final db = await _memDb(); addTearDown(db.close); final c = _container( db: db, optIn: true, availability: ModelAvailability.downloading, ); addTearDown(c.dispose); await tester.pumpWidget(_wrap(c)); await tester.pumpAndSettle(); expect(find.text('AI 제안'), findsOneWidget); expect(find.byTooltip('AI 도움을 먼저 켜주세요'), findsOneWidget); final TextButton btn = tester.widget(find.byType(TextButton).first); expect(btn.onPressed, isNull); }); testWidgets( 'AC6: optIn=true, availability=ready → button enabled, no tooltip wrapper', (tester) async { final db = await _memDb(); addTearDown(db.close); final c = _container( db: db, optIn: true, availability: ModelAvailability.ready, ); addTearDown(c.dispose); await tester.pumpWidget(_wrap(c)); await tester.pumpAndSettle(); expect(find.text('AI 제안'), findsOneWidget); expect(find.byTooltip('AI 도움을 먼저 켜주세요'), findsNothing); final TextButton btn = tester.widget(find.byType(TextButton).first); expect(btn.onPressed, isNotNull); }); }