multi_view_tree_updates_test.dart 7.55 KB
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';

void main() {
  testWidgetsWithLeakTracking('Widgets in view update as expected', (WidgetTester tester) async {
    final Widget widget = View(
      view: tester.view,
      child: const TestWidget(),
    );

    await pumpWidgetWithoutViewWrapper(
      tester: tester,
      widget: widget,
    );

    expect(find.text('Hello'), findsOneWidget);
    expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'Hello');

    tester.state<TestWidgetState>(find.byType(TestWidget)).text = 'World';
    await tester.pump();
    expect(find.text('Hello'), findsNothing);
    expect(find.text('World'), findsOneWidget);
    expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'World');

    await pumpWidgetWithoutViewWrapper(
      tester: tester,
      widget: ViewCollection(
        views: <Widget>[widget],
      ),
    );
    expect(find.text('Hello'), findsNothing);
    expect(find.text('World'), findsOneWidget);
    expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'World');

    tester.state<TestWidgetState>(find.byType(TestWidget)).text = 'FooBar';
    await pumpWidgetWithoutViewWrapper(
      tester: tester,
      widget: widget,
    );
    expect(find.text('World'), findsNothing);
    expect(find.text('FooBar'), findsOneWidget);
    expect(tester.renderObject<RenderParagraph>(find.byType(Text)).text.toPlainText(), 'FooBar');
  });

  testWidgetsWithLeakTracking('Views in ViewCollection update as expected', (WidgetTester tester) async {
    Iterable<String> renderParagraphTexts() {
      return tester.renderObjectList<RenderParagraph>(find.byType(Text)).map((RenderParagraph r) => r.text.toPlainText());
    }

    final Key key1 = UniqueKey();
    final Key key2 = UniqueKey();
    final Widget view1 = View(
      view: tester.view,
      child: TestWidget(key: key1),
    );
    final Widget view2 = View(
      view: FakeView(tester.view),
      child: TestWidget(key: key2),
    );

    await pumpWidgetWithoutViewWrapper(
      tester: tester,
      widget: ViewCollection(
        views: <Widget>[view1, view2],
      ),
    );

    expect(find.text('Hello'), findsNWidgets(2));
    expect(renderParagraphTexts(), <String>['Hello', 'Hello']);

    tester.state<TestWidgetState>(find.byKey(key1)).text = 'Guten';
    tester.state<TestWidgetState>(find.byKey(key2)).text = 'Tag';
    await tester.pump();
    expect(find.text('Hello'), findsNothing);
    expect(find.text('Guten'), findsOneWidget);
    expect(find.text('Tag'), findsOneWidget);
    expect(renderParagraphTexts(), <String>['Guten', 'Tag']);

    tester.state<TestWidgetState>(find.byKey(key2)).text = 'Abend';
    await tester.pump();
    expect(find.text('Tag'), findsNothing);
    expect(find.text('Guten'), findsOneWidget);
    expect(find.text('Abend'), findsOneWidget);
    expect(renderParagraphTexts(), <String>['Guten', 'Abend']);

    tester.state<TestWidgetState>(find.byKey(key2)).text = 'Morgen';
    await pumpWidgetWithoutViewWrapper(
      tester: tester,
      widget: ViewCollection(
        views: <Widget>[view1, ViewCollection(views: <Widget>[view2])],
      ),
    );
    expect(find.text('Abend'), findsNothing);
    expect(find.text('Guten'), findsOneWidget);
    expect(find.text('Morgen'), findsOneWidget);
    expect(renderParagraphTexts(), <String>['Guten', 'Morgen']);
  });

  testWidgetsWithLeakTracking('Views in ViewAnchor update as expected', (WidgetTester tester) async {
    Iterable<String> renderParagraphTexts() {
      return tester.renderObjectList<RenderParagraph>(find.byType(Text)).map((RenderParagraph r) => r.text.toPlainText());
    }

    final Key insideAnchoredViewKey = UniqueKey();
    final Key outsideAnchoredViewKey = UniqueKey();
    final Widget view = View(
      view: FakeView(tester.view),
      child: TestWidget(key: insideAnchoredViewKey),
    );

    await tester.pumpWidget(
      ViewAnchor(
        view: view,
        child: TestWidget(key: outsideAnchoredViewKey),
      ),
    );

    expect(find.text('Hello'), findsNWidgets(2));
    expect(renderParagraphTexts(), <String>['Hello', 'Hello']);

    tester.state<TestWidgetState>(find.byKey(outsideAnchoredViewKey)).text = 'Guten';
    tester.state<TestWidgetState>(find.byKey(insideAnchoredViewKey)).text = 'Tag';
    await tester.pump();
    expect(find.text('Hello'), findsNothing);
    expect(find.text('Guten'), findsOneWidget);
    expect(find.text('Tag'), findsOneWidget);
    expect(renderParagraphTexts(), <String>['Guten', 'Tag']);

    tester.state<TestWidgetState>(find.byKey(insideAnchoredViewKey)).text = 'Abend';
    await tester.pump();
    expect(find.text('Tag'), findsNothing);
    expect(find.text('Guten'), findsOneWidget);
    expect(find.text('Abend'), findsOneWidget);
    expect(renderParagraphTexts(), <String>['Guten', 'Abend']);

    tester.state<TestWidgetState>(find.byKey(outsideAnchoredViewKey)).text = 'Schönen';
    await tester.pump();
    expect(find.text('Guten'), findsNothing);
    expect(find.text('Schönen'), findsOneWidget);
    expect(find.text('Abend'), findsOneWidget);
    expect(renderParagraphTexts(), <String>['Schönen', 'Abend']);

    tester.state<TestWidgetState>(find.byKey(insideAnchoredViewKey)).text = 'Tag';
    await tester.pumpWidget(
      ViewAnchor(
        view: ViewCollection(views: <Widget>[view]),
        child: TestWidget(key: outsideAnchoredViewKey),
      ),
    );
    await tester.pump();
    expect(find.text('Abend'), findsNothing);
    expect(find.text('Schönen'), findsOneWidget);
    expect(find.text('Tag'), findsOneWidget);
    expect(renderParagraphTexts(), <String>['Schönen', 'Tag']);

    tester.state<TestWidgetState>(find.byKey(insideAnchoredViewKey)).text = 'Morgen';
    await tester.pumpWidget(
      SizedBox(
        child: ViewAnchor(
          view: ViewCollection(views: <Widget>[view]),
          child: TestWidget(key: outsideAnchoredViewKey),
        ),
      ),
    );
    await tester.pump();
    expect(find.text('Schönen'), findsNothing); // The `outsideAnchoredViewKey` is not a global key, its state is lost in the move above.
    expect(find.text('Tag'), findsNothing);
    expect(find.text('Hello'), findsOneWidget);
    expect(find.text('Morgen'), findsOneWidget);
    expect(renderParagraphTexts(), <String>['Hello', 'Morgen']);
  });
}

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

  @override
  State<TestWidget> createState() => TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {
  String get text => _text;
  String _text = 'Hello';
  set text(String value) {
    if (_text == value) {
      return;
    }
    setState(() {
      _text = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text(text, textDirection: TextDirection.ltr);
  }
}

Future<void> pumpWidgetWithoutViewWrapper({required WidgetTester tester, required  Widget widget}) {
  tester.binding.attachRootWidget(widget);
  tester.binding.scheduleFrame();
  return tester.binding.pump();
}

class FakeView extends TestFlutterView{
  FakeView(FlutterView view, { this.viewId = 100 }) : super(
    view: view,
    platformDispatcher: view.platformDispatcher as TestPlatformDispatcher,
    display: view.display as TestDisplay,
  );

  @override
  final int viewId;
}