From 3a6e906dc1a4b16fe416cb3d91148bb58dc7cf48 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 13 May 2026 07:25:17 +0200 Subject: [PATCH] test sweep: cover welcome_view tail paths (T-91) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eight new widget tests in test/builtin/welcome/widget_test.dart covering the WelcomeView paths the existing render-it test didn't reach: - TIPS card visibility: shown when viewport height > 640, hidden when shorter (LayoutBuilder branch). - Status line tri-state: "checking…" before toolchain resolution, "application ok" when all tools present, missing-tools listing when some are absent. - Theme-name link tap fires the theme.pick command via the registry. - Open folder tap with no native picker (returns null, function returns early) and with MissingPluginException (falls through to the path dialog via kernel.dialog.show). Coverage: builtin/welcome/src/welcome_view.dart 84/221 -> 120/221 (54%). Remaining 101 lines are inside the _OpenProjectDialog, _NotARepoDialog, and recent-project rows — they need either a DialogHost in the test harness or a populated recents list, which is materially more setup than the rest of this batch's pattern. Total coverage 79.34% -> 79.80%. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/builtin/welcome/widget_test.dart | 104 ++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/test/builtin/welcome/widget_test.dart b/test/builtin/welcome/widget_test.dart index 6b8efa51..16fb010a 100644 --- a/test/builtin/welcome/widget_test.dart +++ b/test/builtin/welcome/widget_test.dart @@ -2,8 +2,10 @@ import 'dart:ui'; import 'package:clide/builtin/welcome/welcome.dart'; import 'package:clide/builtin/welcome/src/welcome_view.dart'; +import 'package:clide/clide.dart'; import 'package:clide/extension/extension.dart'; import 'package:clide/kernel/kernel.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -47,5 +49,107 @@ void main() { expect(find.text('IDE for Claude Code CLI'), findsOneWidget); expect(find.text('Open folder…'), findsOneWidget); }); + + testWidgets('TIPS card renders when the viewport is tall enough', (tester) async { + tester.view.physicalSize = const Size(1200, 800); + tester.view.devicePixelRatio = 1.0; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + await tester.pumpWidget(harness(f, const WelcomeView())); + await tester.pumpAndSettle(); + expect(find.text('TIPS'), findsOneWidget); + expect(find.text('Quick open'), findsOneWidget); + }); + + testWidgets('TIPS card is hidden when the viewport is short', (tester) async { + tester.view.physicalSize = const Size(1200, 500); + tester.view.devicePixelRatio = 1.0; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + await tester.pumpWidget(harness(f, const WelcomeView())); + await tester.pumpAndSettle(); + expect(find.text('TIPS'), findsNothing); + }); + + testWidgets('status line shows "checking…" before toolchain resolution', (tester) async { + await tester.pumpWidget(harness(f, const WelcomeView())); + expect(find.text('checking…'), findsOneWidget); + }); + + testWidgets('status line shows "application ok" when all tools resolved', (tester) async { + f.services.toolchain.applyResolved(const ResolvedPaths( + git: '/usr/bin/git', + pql: '/usr/bin/pql', + tmux: '/usr/bin/tmux', + shell: '/bin/bash', + )); + await tester.pumpWidget(harness(f, const WelcomeView())); + await tester.pumpAndSettle(); + expect(find.text('application ok'), findsOneWidget); + }); + + testWidgets('status line lists missing tools when some are absent', (tester) async { + tester.view.physicalSize = const Size(1600, 900); + tester.view.devicePixelRatio = 1.0; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + f.services.toolchain.applyResolved(const ResolvedPaths( + pql: '/usr/bin/pql', + )); + await tester.pumpWidget(harness(f, const WelcomeView())); + await tester.pumpAndSettle(); + expect(find.textContaining('git not found'), findsOneWidget); + expect(find.textContaining('tmux not found'), findsOneWidget); + }); + + testWidgets('theme-name link fires the theme.pick command when tapped', (tester) async { + var invocations = 0; + f.services.commands.register(CommandContribution( + id: 'theme.pick', + command: 'theme.pick', + title: 'Theme: Pick', + run: (_) async { + invocations++; + return IpcResponse.ok(id: '', data: const {}); + }, + )); + await tester.pumpWidget(harness(f, const WelcomeView())); + await tester.pumpAndSettle(); + await tester.tap(find.textContaining('theme:')); + await tester.pump(); + expect(invocations, 1); + }); + + testWidgets('Open folder tap kicks off the picker flow without throwing', (tester) async { + await tester.pumpWidget(harness(f, const WelcomeView())); + await tester.pumpAndSettle(); + await tester.tap(find.text('Open folder…')); + await tester.pumpAndSettle(); + // In the test harness, the platform channel returns null (no native + // picker, no MissingPluginException), so the function returns + // early without raising. The point is just to exercise the path. + expect(tester.takeException(), isNull); + }); + + testWidgets('Open folder opens the fallback dialog when the picker throws MissingPluginException', (tester) async { + // Pre-register a mock that throws — emulating a platform without + // native picker support. + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('clide/window'), + (call) async { + if (call.method == 'pickDirectory') { + throw MissingPluginException(); + } + return null; + }, + ); + await tester.pumpWidget(harness(f, const WelcomeView())); + await tester.pumpAndSettle(); + await tester.tap(find.text('Open folder…')); + await tester.pumpAndSettle(); + expect(f.services.dialog.isOpen, isTrue); + f.services.dialog.dismiss(); + await tester.pumpAndSettle(); + }); }); }