Saltar al contenido principal

Tocar, arrastrar e ingresar texto

Cómo probar widgets para la interacción del usuario.

Muchos widgets no solo muestran información, sino que también responden a la interacción del usuario. Esto incluye botones que se pueden tocar y un TextField para ingresar texto.

Para probar estas interacciones, necesitas una forma de simularlas en el entorno de pruebas. Para este propósito, utiliza la biblioteca WidgetTester.

El WidgetTester proporciona métodos para ingresar texto, tocar y arrastrar.

En muchos casos, las interacciones del usuario actualizan el estado de la aplicación. En el entorno de pruebas, Flutter no reconstruye automáticamente los widgets cuando cambia el estado. Para asegurar que el árbol de widgets se reconstruya después de simular una interacción del usuario, llama a los métodos pump() o pumpAndSettle() proporcionados por el WidgetTester. Esta receta utiliza los siguientes pasos:

  1. Crea un widget para probar.
  2. Ingresar texto en el campo de texto.
  3. Asegurar que al tocar un botón se añada el pendiente (todo).
  4. Asegurar que la acción de deslizar para descartar (swipe-to-dismiss) elimine el pendiente (todo).

1. Crea un widget para probar

#

Para este ejemplo, crea una aplicación de pendientes (todo) básica que pruebe tres características:

  1. Ingresar texto en un TextField.
  2. Tocar un FloatingActionButton para añadir el texto a una lista de pendientes (todos).
  3. Deslizar para descartar para eliminar el elemento de la lista.

Para mantener el enfoque en las pruebas, esta receta no proporcionará una guía detallada sobre cómo construir la aplicación de pendientes. Para obtener más información sobre cómo se construye esta aplicación, consulta las recetas correspondientes:

dart
class TodoList extends StatefulWidget {
  const TodoList({super.key});

  @override
  State<TodoList> createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {
  static const _appTitle = 'Todo List';
  final todos = <String>[];
  final controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _appTitle,
      home: Scaffold(
        appBar: AppBar(title: const Text(_appTitle)),
        body: Column(
          children: [
            TextField(controller: controller),
            Expanded(
              child: ListView.builder(
                itemCount: todos.length,
                itemBuilder: (context, index) {
                  final todo = todos[index];

                  return Dismissible(
                    key: Key('$todo$index'),
                    onDismissed: (direction) => todos.removeAt(index),
                    background: Container(color: Colors.red),
                    child: ListTile(title: Text(todo)),
                  );
                },
              ),
            ),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              todos.add(controller.text);
              controller.clear();
            });
          },
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}

2. Ingresar texto en el campo de texto

#

Ahora que tienes una aplicación de pendientes, comienza a escribir la prueba. Empieza por ingresar texto en el TextField.

Completa esta tarea:

  1. Construir el widget en el entorno de pruebas.
  2. Usando el método enterText() de WidgetTester.
dart
testWidgets('Add and remove a todo', (tester) async {
  // Build the widget
  await tester.pumpWidget(const TodoList());

  // Enter 'hi' into the TextField.
  await tester.enterText(find.byType(TextField), 'hi');
});

3. Asegurar que al tocar un botón se añada el pendiente (todo)

#

Después de ingresar texto en el TextField, asegúrate de que al tocar el FloatingActionButton se añada el elemento a la lista.

Esto implica tres pasos:

  1. Toca el botón de añadir usando el método tap() .
  2. Reconstruye el widget después de que el estado haya cambiado usando el método pump().
  3. Asegurar que el elemento de la lista aparezca en pantalla.
dart
testWidgets('Add and remove a todo', (tester) async {
  // Enter text code...

  // Tap the add button.
  await tester.tap(find.byType(FloatingActionButton));

  // Rebuild the widget after the state has changed.
  await tester.pump();

  // Expect to find the item on screen.
  expect(find.text('hi'), findsOneWidget);
});

4. Asegurar que la acción de deslizar para descartar (swipe-to-dismiss) elimine el pendiente (todo)

#

Finalmente, asegúrate de que al realizar una acción de deslizar para descartar en el elemento pendiente lo elimine de la lista. Esto implica tres pasos:

  1. Usa el método drag() para realizar una acción de deslizar para descartar.
  2. Usa el método pumpAndSettle() para reconstruir continuamente el árbol de widgets hasta que la animación de descarte se complete.
  3. Asegurar que el elemento ya no aparezca en pantalla.
dart
testWidgets('Add and remove a todo', (tester) async {
  // Enter text and add the item...

  // Swipe the item to dismiss it.
  await tester.drag(find.byType(Dismissible), const Offset(500, 0));

  // Build the widget until the dismiss animation ends.
  await tester.pumpAndSettle();

  // Ensure that the item is no longer on screen.
  expect(find.text('hi'), findsNothing);
});

Ejemplo completo

#
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('Add and remove a todo', (tester) async {
    // Build the widget.
    await tester.pumpWidget(const TodoList());

    // Enter 'hi' into the TextField.
    await tester.enterText(find.byType(TextField), 'hi');

    // Tap the add button.
    await tester.tap(find.byType(FloatingActionButton));

    // Rebuild the widget with the new item.
    await tester.pump();

    // Expect to find the item on screen.
    expect(find.text('hi'), findsOneWidget);

    // Swipe the item to dismiss it.
    await tester.drag(find.byType(Dismissible), const Offset(500, 0));

    // Build the widget until the dismiss animation ends.
    await tester.pumpAndSettle();

    // Ensure that the item is no longer on screen.
    expect(find.text('hi'), findsNothing);
  });
}

class TodoList extends StatefulWidget {
  const TodoList({super.key});

  @override
  State<TodoList> createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {
  static const _appTitle = 'Todo List';
  final todos = <String>[];
  final controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _appTitle,
      home: Scaffold(
        appBar: AppBar(title: const Text(_appTitle)),
        body: Column(
          children: [
            TextField(controller: controller),
            Expanded(
              child: ListView.builder(
                itemCount: todos.length,
                itemBuilder: (context, index) {
                  final todo = todos[index];

                  return Dismissible(
                    key: Key('$todo$index'),
                    onDismissed: (direction) => todos.removeAt(index),
                    background: Container(color: Colors.red),
                    child: ListTile(title: Text(todo)),
                  );
                },
              ),
            ),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              todos.add(controller.text);
              controller.clear();
            });
          },
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}