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

5 6
// @dart = 2.8

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

10 11
import 'test_widgets.dart';

12
class TestInherited extends InheritedWidget {
13
  const TestInherited({ Key key, Widget child, this.shouldNotify = true })
14 15 16 17 18 19 20 21 22 23
    : super(key: key, child: child);

  final bool shouldNotify;

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

24
class ValueInherited extends InheritedWidget {
25
  const ValueInherited({ Key key, Widget child, this.value })
26 27 28 29 30 31 32 33
    : super(key: key, child: child);

  final int value;

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

34
class ExpectFail extends StatefulWidget {
35
  const ExpectFail(this.onError, { Key key }) : super(key: key);
36 37 38
  final VoidCallback onError;

  @override
39
  ExpectFailState createState() => ExpectFailState();
40 41 42 43 44 45 46
}

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

  @override
54
  Widget build(BuildContext context) => Container();
55 56
}

57 58 59 60 61
class ChangeNotifierInherited extends InheritedNotifier<ChangeNotifier> {
  const ChangeNotifierInherited({ Key key, Widget child, ChangeNotifier notifier })
    : super(key: key, child: child, notifier: notifier);
}

62
void main() {
63
  testWidgets('Inherited notifies dependents', (WidgetTester tester) async {
64
    final List<TestInherited> log = <TestInherited>[];
65

66
    final Builder builder = Builder(
67
      builder: (BuildContext context) {
68
        log.add(context.dependOnInheritedWidgetOfExactType<TestInherited>());
69
        return Container();
70 71
      }
    );
72

73
    final TestInherited first = TestInherited(child: builder);
74
    await tester.pumpWidget(first);
75

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

78
    final TestInherited second = TestInherited(child: builder, shouldNotify: false);
79
    await tester.pumpWidget(second);
80

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

83
    final TestInherited third = TestInherited(child: builder, shouldNotify: true);
84
    await tester.pumpWidget(third);
85

86
    expect(log, equals(<TestInherited>[first, third]));
87 88
  });

89
  testWidgets('Update inherited when reparenting state', (WidgetTester tester) async {
90
    final GlobalKey globalKey = GlobalKey();
91
    final List<TestInherited> log = <TestInherited>[];
92 93

    TestInherited build() {
94 95 96
      return TestInherited(
        key: UniqueKey(),
        child: Container(
97
          key: globalKey,
98
          child: Builder(
99
            builder: (BuildContext context) {
100
              log.add(context.dependOnInheritedWidgetOfExactType<TestInherited>());
101
              return Container();
102
            }
103 104
          ),
        ),
105 106
      );
    }
107

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

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

113
    final TestInherited second = build();
114
    await tester.pumpWidget(second);
115

116
    expect(log, equals(<TestInherited>[first, second]));
117
  });
118

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

122
    await tester.pumpWidget(
123 124
      Container(
        child: ValueInherited(
125
          value: 1,
126 127 128 129
          child: Container(
            child: FlipWidget(
              left: Container(
                child: ValueInherited(
130
                  value: 2,
131 132
                  child: Container(
                    child: ValueInherited(
133
                      value: 3,
134 135
                      child: Container(
                        child: Builder(
136
                          builder: (BuildContext context) {
137
                            final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>();
138
                            log.add('a: ${v.value}');
Ian Hickson's avatar
Ian Hickson committed
139
                            return const Text('', textDirection: TextDirection.ltr);
140
                          }
141 142 143 144 145
                        ),
                      ),
                    ),
                  ),
                ),
146
              ),
147 148
              right: Container(
                child: ValueInherited(
149
                  value: 2,
150 151 152
                  child: Container(
                    child: Container(
                      child: Builder(
153
                        builder: (BuildContext context) {
154
                          final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>();
155
                          log.add('b: ${v.value}');
Ian Hickson's avatar
Ian Hickson committed
156
                          return const Text('', textDirection: TextDirection.ltr);
157
                        }
158 159 160 161 162 163 164 165
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
166
      ),
167 168 169 170 171
    );

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

172
    await tester.pump();
173 174 175 176 177

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

    flipStatefulWidget(tester);
178
    await tester.pump();
179 180 181 182 183

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

    flipStatefulWidget(tester);
184
    await tester.pump();
185 186 187 188 189

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

190
  testWidgets('Update inherited when removing node and child has global key', (WidgetTester tester) async {
191 192 193

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

194
    final Key key = GlobalKey();
195

196
    await tester.pumpWidget(
197 198
      Container(
        child: ValueInherited(
199
          value: 1,
200 201 202 203
          child: Container(
            child: FlipWidget(
              left: Container(
                child: ValueInherited(
204
                  value: 2,
205 206
                  child: Container(
                    child: ValueInherited(
207
                      value: 3,
208
                      child: Container(
209
                        key: key,
210
                        child: Builder(
211
                          builder: (BuildContext context) {
212
                            final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>();
213
                            log.add('a: ${v.value}');
Ian Hickson's avatar
Ian Hickson committed
214
                            return const Text('', textDirection: TextDirection.ltr);
215
                          }
216 217 218 219 220
                        ),
                      ),
                    ),
                  ),
                ),
221
              ),
222 223
              right: Container(
                child: ValueInherited(
224
                  value: 2,
225 226
                  child: Container(
                    child: Container(
227
                      key: key,
228
                      child: Builder(
229
                        builder: (BuildContext context) {
230
                          final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>();
231
                          log.add('b: ${v.value}');
Ian Hickson's avatar
Ian Hickson committed
232
                          return const Text('', textDirection: TextDirection.ltr);
233
                        }
234 235 236 237 238 239 240 241
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
242
      ),
243 244 245 246 247
    );

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

248
    await tester.pump();
249 250 251 252 253

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

    flipStatefulWidget(tester);
254
    await tester.pump();
255 256 257 258 259

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

    flipStatefulWidget(tester);
260
    await tester.pump();
261 262 263 264 265

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

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

269
    final Key key = GlobalKey();
270

271
    final Widget child = Builder(
272
      builder: (BuildContext context) {
273
        final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>();
274
        log.add(v.value);
Ian Hickson's avatar
Ian Hickson committed
275
        return const Text('', textDirection: TextDirection.ltr);
276 277 278
      }
    );

279
    await tester.pumpWidget(
280 281
      Container(
        child: ValueInherited(
282
          value: 1,
283 284 285 286
          child: Container(
            child: FlipWidget(
              left: Container(
                child: ValueInherited(
287
                  value: 2,
288 289
                  child: Container(
                    child: ValueInherited(
290
                      value: 3,
291
                      child: Container(
292
                        key: key,
293 294 295 296 297
                        child: child,
                      ),
                    ),
                  ),
                ),
298
              ),
299 300
              right: Container(
                child: ValueInherited(
301
                  value: 2,
302 303
                  child: Container(
                    child: Container(
304
                      key: key,
305 306 307 308 309 310 311 312
                      child: child,
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
313
      ),
314 315 316 317 318
    );

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

319
    await tester.pump();
320 321 322 323 324

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

    flipStatefulWidget(tester);
325
    await tester.pump();
326 327 328 329 330

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

    flipStatefulWidget(tester);
331
    await tester.pump();
332 333 334 335 336

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

337
  testWidgets('Update inherited when removing node and child has global key with constant child, minimised', (WidgetTester tester) async {
338 339 340

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

341 342
    final Widget child = Builder(
      key: GlobalKey(),
343
      builder: (BuildContext context) {
344
        final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>();
345
        log.add(v.value);
Ian Hickson's avatar
Ian Hickson committed
346
        return const Text('', textDirection: TextDirection.ltr);
347
      },
348 349
    );

350
    await tester.pumpWidget(
351
      ValueInherited(
352
        value: 2,
353 354
        child: FlipWidget(
          left: ValueInherited(
355
            value: 3,
356
            child: child,
357
          ),
358 359
          right: child,
        ),
360
      ),
361 362 363 364 365
    );

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

366
    await tester.pump();
367 368 369 370 371

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

    flipStatefulWidget(tester);
372
    await tester.pump();
373 374 375 376 377

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

    flipStatefulWidget(tester);
378
    await tester.pump();
379 380 381 382 383

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

384
  testWidgets('Inherited widget notifies descendants when descendant previously failed to find a match', (WidgetTester tester) async {
385 386
    int inheritedValue = -1;

387 388 389
    final Widget inner = Container(
      key: GlobalKey(),
      child: Builder(
390
        builder: (BuildContext context) {
391
          final ValueInherited widget = context.dependOnInheritedWidgetOfExactType<ValueInherited>();
392
          inheritedValue = widget?.value;
393
          return Container();
394
        }
395
      ),
396 397
    );

398
    await tester.pumpWidget(
399 400 401 402 403
      inner
    );
    expect(inheritedValue, isNull);

    inheritedValue = -2;
404
    await tester.pumpWidget(
405
      ValueInherited(
406
        value: 3,
407
        child: inner,
408
      ),
409 410 411 412
    );
    expect(inheritedValue, equals(3));
  });

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

416 417 418
    final Widget inner = Container(
      key: GlobalKey(),
      child: Builder(
419 420
        builder: (BuildContext context) {
          buildCount += 1;
421
          return Container();
422
        }
423
      ),
424 425
    );

426
    await tester.pumpWidget(
427 428 429 430
      inner
    );
    expect(buildCount, equals(1));

431
    await tester.pumpWidget(
432
      ValueInherited(
433
        value: 3,
434
        child: inner,
435
      ),
436 437 438 439
    );
    expect(buildCount, equals(1));
  });

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

443 444 445
    final Widget inner = Container(
      key: GlobalKey(),
      child: TestInherited(
446
        shouldNotify: false,
447
        child: Builder(
448
          builder: (BuildContext context) {
449
            context.dependOnInheritedWidgetOfExactType<TestInherited>();
450
            buildCount += 1;
451
            return Container();
452
          }
453 454
        ),
      ),
455 456
    );

457
    await tester.pumpWidget(
458 459 460 461
      inner
    );
    expect(buildCount, equals(1));

462
    await tester.pumpWidget(
463
      ValueInherited(
464
        value: 3,
465
        child: inner,
466
      ),
467 468 469
    );
    expect(buildCount, equals(2));
  });
470 471 472 473 474

  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;

475
    final TestInherited parent = TestInherited(child: ExpectFail(() {
476 477 478 479 480 481
      exceptionCaught = true;
    }));
    await tester.pumpWidget(parent);

    expect(exceptionCaught, isTrue);
  });
482 483 484 485 486 487 488

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

    final Widget builder = Builder(
      builder: (BuildContext context) {
489
        context.dependOnInheritedWidgetOfExactType<ChangeNotifierInherited>();
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
        buildCount += 1;
        return Container();
      }
    );

    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));

508
    notifier.notifyListeners();
509 510 511 512 513 514 515 516 517 518 519 520
    await tester.pump();
    expect(buildCount, equals(2));

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

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