inherited_test.dart 12.9 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
6
import 'package:flutter_test/flutter_test.dart';
7

8 9
import 'test_widgets.dart';

10
class TestInherited extends InheritedWidget {
11
  const TestInherited({ super.key, required super.child, this.shouldNotify = true });
12 13 14 15 16 17 18 19 20

  final bool shouldNotify;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return shouldNotify;
  }
}

21
class ValueInherited extends InheritedWidget {
22
  const ValueInherited({ super.key, required super.child, required this.value });
23 24 25 26 27 28 29

  final int value;

  @override
  bool updateShouldNotify(ValueInherited oldWidget) => value != oldWidget.value;
}

30
class ExpectFail extends StatefulWidget {
31
  const ExpectFail(this.onError, { super.key });
32 33 34
  final VoidCallback onError;

  @override
35
  ExpectFailState createState() => ExpectFailState();
36 37 38 39 40 41 42
}

class ExpectFailState extends State<ExpectFail> {
  @override
  void initState() {
    super.initState();
    try {
43
      context.dependOnInheritedWidgetOfExactType<TestInherited>(); // should fail
44
    } catch (e) {
45
      widget.onError();
46 47 48 49
    }
  }

  @override
50
  Widget build(BuildContext context) => Container();
51 52
}

53
class ChangeNotifierInherited extends InheritedNotifier<ChangeNotifier> {
54
  const ChangeNotifierInherited({ super.key, required super.child, super.notifier });
55 56
}

57
void main() {
58
  testWidgets('Inherited notifies dependents', (WidgetTester tester) async {
59
    final List<TestInherited> log = <TestInherited>[];
60

61
    final Builder builder = Builder(
62
      builder: (BuildContext context) {
63
        log.add(context.dependOnInheritedWidgetOfExactType<TestInherited>()!);
64
        return Container();
65
      },
66
    );
67

68
    final TestInherited first = TestInherited(child: builder);
69
    await tester.pumpWidget(first);
70

71
    expect(log, equals(<TestInherited>[first]));
72

73
    final TestInherited second = TestInherited(shouldNotify: false, child: builder);
74
    await tester.pumpWidget(second);
75

76
    expect(log, equals(<TestInherited>[first]));
77

78
    final TestInherited third = TestInherited(child: builder);
79
    await tester.pumpWidget(third);
80

81
    expect(log, equals(<TestInherited>[first, third]));
82 83
  });

84
  testWidgets('Update inherited when reparenting state', (WidgetTester tester) async {
85
    final GlobalKey globalKey = GlobalKey();
86
    final List<TestInherited> log = <TestInherited>[];
87 88

    TestInherited build() {
89 90 91
      return TestInherited(
        key: UniqueKey(),
        child: Container(
92
          key: globalKey,
93
          child: Builder(
94
            builder: (BuildContext context) {
95
              log.add(context.dependOnInheritedWidgetOfExactType<TestInherited>()!);
96
              return Container();
97
            },
98 99
          ),
        ),
100 101
      );
    }
102

103
    final TestInherited first = build();
104
    await tester.pumpWidget(first);
105

106
    expect(log, equals(<TestInherited>[first]));
107

108
    final TestInherited second = build();
109
    await tester.pumpWidget(second);
110

111
    expect(log, equals(<TestInherited>[first, second]));
112
  });
113

114
  testWidgets('Update inherited when removing node', (WidgetTester tester) async {
115 116
    final List<String> log = <String>[];

117
    await tester.pumpWidget(
118 119 120 121 122 123 124 125 126 127 128 129
      ValueInherited(
        value: 1,
        child: FlipWidget(
          left: ValueInherited(
            value: 2,
            child: ValueInherited(
              value: 3,
              child: Builder(
                builder: (BuildContext context) {
                  final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
                  log.add('a: ${v.value}');
                  return const Text('', textDirection: TextDirection.ltr);
130
                },
131 132 133
              ),
            ),
          ),
134 135 136 137 138 139 140
          right: ValueInherited(
            value: 2,
            child: Builder(
              builder: (BuildContext context) {
                final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
                log.add('b: ${v.value}');
                return const Text('', textDirection: TextDirection.ltr);
141
              },
142 143
            ),
          ),
144
        ),
145
      ),
146 147 148 149 150
    );

    expect(log, equals(<String>['a: 3']));
    log.clear();

151
    await tester.pump();
152 153 154 155 156

    expect(log, equals(<String>[]));
    log.clear();

    flipStatefulWidget(tester);
157
    await tester.pump();
158 159 160 161 162

    expect(log, equals(<String>['b: 2']));
    log.clear();

    flipStatefulWidget(tester);
163
    await tester.pump();
164 165 166 167 168

    expect(log, equals(<String>['a: 3']));
    log.clear();
  });

169
  testWidgets('Update inherited when removing node and child has global key', (WidgetTester tester) async {
170 171 172

    final List<String> log = <String>[];

173
    final Key key = GlobalKey();
174

175
    await tester.pumpWidget(
176 177 178 179 180 181 182 183 184 185 186 187 188 189
      ValueInherited(
        value: 1,
        child: FlipWidget(
          left: ValueInherited(
            value: 2,
            child: ValueInherited(
              value: 3,
              child: Container(
                key: key,
                child: Builder(
                  builder: (BuildContext context) {
                    final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
                    log.add('a: ${v.value}');
                    return const Text('', textDirection: TextDirection.ltr);
190
                  },
191
                ),
192
              ),
193 194 195 196 197 198 199 200 201 202 203
            ),
          ),
          right: ValueInherited(
            value: 2,
            child: Container(
              key: key,
              child: Builder(
                builder: (BuildContext context) {
                  final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
                  log.add('b: ${v.value}');
                  return const Text('', textDirection: TextDirection.ltr);
204
                },
205 206 207 208
              ),
            ),
          ),
        ),
209
      ),
210 211 212 213 214
    );

    expect(log, equals(<String>['a: 3']));
    log.clear();

215
    await tester.pump();
216 217 218 219 220

    expect(log, equals(<String>[]));
    log.clear();

    flipStatefulWidget(tester);
221
    await tester.pump();
222 223 224 225 226

    expect(log, equals(<String>['b: 2']));
    log.clear();

    flipStatefulWidget(tester);
227
    await tester.pump();
228 229 230 231 232

    expect(log, equals(<String>['a: 3']));
    log.clear();
  });

233
  testWidgets('Update inherited when removing node and child has global key with constant child', (WidgetTester tester) async {
234 235
    final List<int> log = <int>[];

236
    final Key key = GlobalKey();
237

238
    final Widget child = Builder(
239
      builder: (BuildContext context) {
240
        final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
241
        log.add(v.value);
Ian Hickson's avatar
Ian Hickson committed
242
        return const Text('', textDirection: TextDirection.ltr);
243
      },
244 245
    );

246
    await tester.pumpWidget(
247 248 249 250 251 252 253 254 255 256
      ValueInherited(
        value: 1,
        child: FlipWidget(
          left: ValueInherited(
            value: 2,
            child: ValueInherited(
              value: 3,
              child: Container(
                key: key,
                child: child,
257 258 259
              ),
            ),
          ),
260 261 262 263 264 265 266
          right: ValueInherited(
            value: 2,
            child: Container(
              key: key,
              child: child,
            ),
          ),
267
        ),
268
      ),
269 270 271 272 273
    );

    expect(log, equals(<int>[3]));
    log.clear();

274
    await tester.pump();
275 276 277 278 279

    expect(log, equals(<int>[]));
    log.clear();

    flipStatefulWidget(tester);
280
    await tester.pump();
281 282 283 284 285

    expect(log, equals(<int>[2]));
    log.clear();

    flipStatefulWidget(tester);
286
    await tester.pump();
287 288 289 290 291

    expect(log, equals(<int>[3]));
    log.clear();
  });

292
  testWidgets('Update inherited when removing node and child has global key with constant child, minimised', (WidgetTester tester) async {
293 294 295

    final List<int> log = <int>[];

296 297
    final Widget child = Builder(
      key: GlobalKey(),
298
      builder: (BuildContext context) {
299
        final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
300
        log.add(v.value);
Ian Hickson's avatar
Ian Hickson committed
301
        return const Text('', textDirection: TextDirection.ltr);
302
      },
303 304
    );

305
    await tester.pumpWidget(
306
      ValueInherited(
307
        value: 2,
308 309
        child: FlipWidget(
          left: ValueInherited(
310
            value: 3,
311
            child: child,
312
          ),
313 314
          right: child,
        ),
315
      ),
316 317 318 319 320
    );

    expect(log, equals(<int>[3]));
    log.clear();

321
    await tester.pump();
322 323 324 325 326

    expect(log, equals(<int>[]));
    log.clear();

    flipStatefulWidget(tester);
327
    await tester.pump();
328 329 330 331 332

    expect(log, equals(<int>[2]));
    log.clear();

    flipStatefulWidget(tester);
333
    await tester.pump();
334 335 336 337 338

    expect(log, equals(<int>[3]));
    log.clear();
  });

339
  testWidgets('Inherited widget notifies descendants when descendant previously failed to find a match', (WidgetTester tester) async {
340
    int? inheritedValue = -1;
341

342 343 344
    final Widget inner = Container(
      key: GlobalKey(),
      child: Builder(
345
        builder: (BuildContext context) {
346
          final ValueInherited? widget = context.dependOnInheritedWidgetOfExactType<ValueInherited>();
347
          inheritedValue = widget?.value;
348
          return Container();
349
        },
350
      ),
351 352
    );

353
    await tester.pumpWidget(
354
      inner,
355 356 357 358
    );
    expect(inheritedValue, isNull);

    inheritedValue = -2;
359
    await tester.pumpWidget(
360
      ValueInherited(
361
        value: 3,
362
        child: inner,
363
      ),
364 365 366 367
    );
    expect(inheritedValue, equals(3));
  });

368
  testWidgets("Inherited widget doesn't notify descendants when descendant did not previously fail to find a match and had no dependencies", (WidgetTester tester) async {
369 370
    int buildCount = 0;

371 372 373
    final Widget inner = Container(
      key: GlobalKey(),
      child: Builder(
374 375
        builder: (BuildContext context) {
          buildCount += 1;
376
          return Container();
377
        },
378
      ),
379 380
    );

381
    await tester.pumpWidget(
382
      inner,
383 384 385
    );
    expect(buildCount, equals(1));

386
    await tester.pumpWidget(
387
      ValueInherited(
388
        value: 3,
389
        child: inner,
390
      ),
391 392 393 394
    );
    expect(buildCount, equals(1));
  });

395
  testWidgets('Inherited widget does notify descendants when descendant did not previously fail to find a match but did have other dependencies', (WidgetTester tester) async {
396 397
    int buildCount = 0;

398 399 400
    final Widget inner = Container(
      key: GlobalKey(),
      child: TestInherited(
401
        shouldNotify: false,
402
        child: Builder(
403
          builder: (BuildContext context) {
404
            context.dependOnInheritedWidgetOfExactType<TestInherited>();
405
            buildCount += 1;
406
            return Container();
407
          },
408 409
        ),
      ),
410 411
    );

412
    await tester.pumpWidget(
413
      inner,
414 415 416
    );
    expect(buildCount, equals(1));

417
    await tester.pumpWidget(
418
      ValueInherited(
419
        value: 3,
420
        child: inner,
421
      ),
422 423 424
    );
    expect(buildCount, equals(2));
  });
425

426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
  testWidgets("BuildContext.getInheritedWidgetOfExactType doesn't create a dependency", (WidgetTester tester) async {
    int buildCount = 0;
    final GlobalKey<void> inheritedKey = GlobalKey();
    final ChangeNotifier notifier = ChangeNotifier();

    final Widget builder = Builder(
      builder: (BuildContext context) {
        expect(context.getInheritedWidgetOfExactType<ChangeNotifierInherited>(), equals(inheritedKey.currentWidget));
        buildCount += 1;
        return Container();
      },
    );

    final Widget inner = ChangeNotifierInherited(
      key: inheritedKey,
      notifier: notifier,
      child: builder,
    );

    await tester.pumpWidget(inner);
    expect(buildCount, equals(1));
    notifier.notifyListeners();
    await tester.pumpWidget(inner);
    expect(buildCount, equals(1));
  });

452 453 454 455
  testWidgets('initState() dependency on Inherited asserts', (WidgetTester tester) async {
    // This is a regression test for https://github.com/flutter/flutter/issues/5491
    bool exceptionCaught = false;

456
    final TestInherited parent = TestInherited(child: ExpectFail(() {
457 458 459 460 461 462
      exceptionCaught = true;
    }));
    await tester.pumpWidget(parent);

    expect(exceptionCaught, isTrue);
  });
463 464 465 466 467 468 469

  testWidgets('InheritedNotifier', (WidgetTester tester) async {
    int buildCount = 0;
    final ChangeNotifier notifier = ChangeNotifier();

    final Widget builder = Builder(
      builder: (BuildContext context) {
470
        context.dependOnInheritedWidgetOfExactType<ChangeNotifierInherited>();
471 472
        buildCount += 1;
        return Container();
473
      },
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
    );

    final Widget inner = ChangeNotifierInherited(
      notifier: notifier,
      child: builder,
    );
    await tester.pumpWidget(inner);
    expect(buildCount, equals(1));

    await tester.pumpWidget(inner);
    expect(buildCount, equals(1));

    await tester.pump();
    expect(buildCount, equals(1));

489
    notifier.notifyListeners();
490 491 492 493 494 495 496 497 498 499 500
    await tester.pump();
    expect(buildCount, equals(2));

    await tester.pumpWidget(inner);
    expect(buildCount, equals(2));

    await tester.pumpWidget(ChangeNotifierInherited(
      child: builder,
    ));
    expect(buildCount, equals(3));
  });
501
}