inherited_test.dart 13.7 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2015 The Chromium 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 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';

8 9
import 'test_widgets.dart';

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

  final bool shouldNotify;

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

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

  final int value;

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

32
class ExpectFail extends StatefulWidget {
33
  const ExpectFail(this.onError);
34 35 36
  final VoidCallback onError;

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

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

  @override
52
  Widget build(BuildContext context) => Container();
53 54
}

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

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

64
    final Builder builder = Builder(
65 66
      builder: (BuildContext context) {
        log.add(context.inheritFromWidgetOfExactType(TestInherited));
67
        return Container();
68 69
      }
    );
70

71
    final TestInherited first = TestInherited(child: builder);
72
    await tester.pumpWidget(first);
73

74
    expect(log, equals(<TestInherited>[first]));
75

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

79
    expect(log, equals(<TestInherited>[first]));
80

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

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

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

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

106
    final TestInherited first = build();
107
    await tester.pumpWidget(first);
108

109
    expect(log, equals(<TestInherited>[first]));
110

111
    final TestInherited second = build();
112
    await tester.pumpWidget(second);
113

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

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

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

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

170
    await tester.pump();
171 172 173 174 175

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

    flipStatefulWidget(tester);
176
    await tester.pump();
177 178 179 180 181

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

    flipStatefulWidget(tester);
182
    await tester.pump();
183 184 185 186 187

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

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

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

192
    final Key key = GlobalKey();
193

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

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

246
    await tester.pump();
247 248 249 250 251

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

    flipStatefulWidget(tester);
252
    await tester.pump();
253 254 255 256 257

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

    flipStatefulWidget(tester);
258
    await tester.pump();
259 260 261 262 263

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

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

267
    final Key key = GlobalKey();
268

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

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

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

317
    await tester.pump();
318 319 320 321 322

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

    flipStatefulWidget(tester);
323
    await tester.pump();
324 325 326 327 328

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

    flipStatefulWidget(tester);
329
    await tester.pump();
330 331 332 333 334

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

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

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

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

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

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

364
    await tester.pump();
365 366 367 368 369

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

    flipStatefulWidget(tester);
370
    await tester.pump();
371 372 373 374 375

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

    flipStatefulWidget(tester);
376
    await tester.pump();
377 378 379 380 381

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

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

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

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

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

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

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

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

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

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

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

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

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

  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;

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

    expect(exceptionCaught, isTrue);
  });
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505

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

    final Widget builder = Builder(
      builder: (BuildContext context) {
        context.inheritFromWidgetOfExactType(ChangeNotifierInherited);
        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));

506
    notifier.notifyListeners();
507 508 509 510 511 512 513 514 515 516 517 518
    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));
  });
519
}