testdrive 0.1.0 copy "testdrive: ^0.1.0" to clipboard
testdrive: ^0.1.0 copied to clipboard

Flutter SDK for TestDeck. Captures HTTP, logs, navigation, and exceptions from debug builds.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:testdrive/testdrive.dart';

import 'screens/errors_demo.dart';
import 'screens/http_demo.dart';
import 'screens/logs_demo.dart';
import 'screens/navigation_demo.dart';
import 'screens/stress_demo.dart';

void main() {
  // The SDK is debug-only — guard with kDebugMode so it never ships to prod.
  if (kDebugMode) {
    TestDrive.init(
      appName: 'TestDrive Example',
      // All of these are the defaults; shown here to document the surface.
      host: '127.0.0.1',
      port: 9876,
      maxBodyBytes: 1024 * 1024,
      captureBodies: true,
      captureLogs: true,
      captureErrors: true,
      maxEventsPerSecond: 200,
    );
  }
  runApp(const TestDriveExampleApp());
}

class TestDriveExampleApp extends StatelessWidget {
  const TestDriveExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TestDrive Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF7AA2F7),
        useMaterial3: true,
        brightness: Brightness.dark,
      ),
      // Routing all navigation through TestDrive's observer feeds the desktop
      // Navigation tab.
      navigatorObservers: [TestDrive.navigator()],
      home: const HomeScreen(),
    );
  }
}

/// A demo entry: a card on the home screen.
class _Demo {
  const _Demo(this.title, this.subtitle, this.icon, this.builder);
  final String title;
  final String subtitle;
  final IconData icon;
  final WidgetBuilder builder;
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  static final List<_Demo> _demos = [
    _Demo(
      'HTTP capture',
      'GET / POST / image / error / 1 MB truncation',
      Icons.swap_vert,
      (_) => const HttpDemoScreen(),
    ),
    _Demo(
      'Logs',
      'levels · structured payload · developer.log',
      Icons.subject,
      (_) => const LogsDemoScreen(),
    ),
    _Demo(
      'Navigation',
      'push / pop / replace · JSON + non-JSON args',
      Icons.alt_route,
      (_) => const NavigationDemoScreen(),
    ),
    _Demo(
      'Errors',
      'Flutter framework · async Dart · caught + logged',
      Icons.error_outline,
      (_) => const ErrorsDemoScreen(),
    ),
    _Demo(
      'Stress / rate limit',
      'flood the transport to see drops + buffering',
      Icons.bolt,
      (_) => const StressDemoScreen(),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('TestDrive Example'),
        centerTitle: false,
      ),
      body: Column(
        children: [
          const TestDriveStatusPanel(),
          const Divider(height: 1),
          Expanded(
            child: ListView.separated(
              padding: const EdgeInsets.all(12),
              itemCount: _demos.length,
              separatorBuilder: (_, __) => const SizedBox(height: 8),
              itemBuilder: (context, i) {
                final d = _demos[i];
                return Card(
                  margin: EdgeInsets.zero,
                  child: ListTile(
                    leading: CircleAvatar(child: Icon(d.icon)),
                    title: Text(d.title),
                    subtitle: Text(d.subtitle),
                    trailing: const Icon(Icons.chevron_right),
                    onTap: () => Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        settings: RouteSettings(name: '/${d.title}'),
                        builder: d.builder,
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

/// Live view of [TestDrive.status] — connection state and transport counters,
/// refreshed once a second. Shows the SDK working even before the desktop
/// connects (events buffer; the counter ticks up).
class TestDriveStatusPanel extends StatefulWidget {
  const TestDriveStatusPanel({super.key});

  @override
  State<TestDriveStatusPanel> createState() => _TestDriveStatusPanelState();
}

class _TestDriveStatusPanelState extends State<TestDriveStatusPanel> {
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(
      const Duration(seconds: 1),
      (_) => setState(() {}),
    );
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final s = TestDrive.status;
    final connected = s.connected;
    final color = !s.initialized
        ? Colors.grey
        : connected
            ? Colors.greenAccent
            : Colors.amberAccent;
    final label = !s.initialized
        ? 'not initialized'
        : connected
            ? 'connected'
            : 'buffering (desktop offline)';

    return Container(
      width: double.infinity,
      padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
      color: Theme.of(context).colorScheme.surfaceContainerHighest,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Icon(Icons.circle, size: 12, color: color),
              const SizedBox(width: 8),
              Text(
                'testdrive · $label',
                style: const TextStyle(fontWeight: FontWeight.w600),
              ),
              const Spacer(),
              Text('${s.host}:${s.port}',
                  style: Theme.of(context).textTheme.bodySmall),
            ],
          ),
          const SizedBox(height: 8),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              _Chip('buffered', '${s.buffered}'),
              _Chip('flushed', '${s.eventsFlushed}'),
              _Chip('dropped', '${s.eventsDropped}'),
            ],
          ),
        ],
      ),
    );
  }
}

class _Chip extends StatelessWidget {
  const _Chip(this.label, this.value);
  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Chip(
      visualDensity: VisualDensity.compact,
      label: Text('$label: $value'),
    );
  }
}

/// Shared layout for a demo screen: a scrollable column of action buttons.
class DemoScaffold extends StatelessWidget {
  const DemoScaffold({
    super.key,
    required this.title,
    required this.intro,
    required this.children,
  });

  final String title;
  final String intro;
  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text(intro, style: Theme.of(context).textTheme.bodyMedium),
          const SizedBox(height: 16),
          ...children,
        ],
      ),
    );
  }
}

/// A full-width labelled action button used across the demo screens.
class DemoButton extends StatelessWidget {
  const DemoButton(this.label, this.onPressed, {super.key, this.icon});
  final String label;
  final IconData? icon;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: SizedBox(
        width: double.infinity,
        child: FilledButton.tonalIcon(
          onPressed: onPressed,
          icon: Icon(icon ?? Icons.play_arrow),
          label: Align(
            alignment: Alignment.centerLeft,
            child: Text(label),
          ),
        ),
      ),
    );
  }
}

/// Helper to flash a short confirmation without dragging BuildContext across
/// an async gap — callers capture this before awaiting.
void toast(BuildContext context, String message) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text(message), duration: const Duration(seconds: 1)),
  );
}
0
likes
160
points
6
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter SDK for TestDeck. Captures HTTP, logs, navigation, and exceptions from debug builds.

Repository (GitHub)
View/report issues

Topics

#debugging #networking #logging #devtools

License

MIT (license)

Dependencies

flutter

More

Packages that depend on testdrive