inherited_test.dart 12.3 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({ Key? key, required 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, required Widget child, required 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, { Key? key }) : super(key: key);
34 35 36
  final VoidCallback onError;

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

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

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

55
class ChangeNotifierInherited extends InheritedNotifier<ChangeNotifier> {
56
  const ChangeNotifierInherited({ Key? key, required Widget child, ChangeNotifier? notifier })
57 58 59
    : 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
      builder: (BuildContext context) {
66
        log.add(context.dependOnInheritedWidgetOfExactType<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(shouldNotify: false, child: builder);
77
    await tester.pumpWidget(second);
78

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

81
    final TestInherited third = TestInherited(child: builder);
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
            builder: (BuildContext context) {
98
              log.add(context.dependOnInheritedWidgetOfExactType<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 123 124 125 126 127 128 129 130 131 132
      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);
133
                },
134 135 136
              ),
            ),
          ),
137 138 139 140 141 142 143
          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);
144
              },
145 146
            ),
          ),
147
        ),
148
      ),
149 150 151 152 153
    );

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

154
    await tester.pump();
155 156 157 158 159

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

    flipStatefulWidget(tester);
160
    await tester.pump();
161 162 163 164 165

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

    flipStatefulWidget(tester);
166
    await tester.pump();
167 168 169 170 171

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

172
  testWidgets('Update inherited when removing node and child has global key', (WidgetTester tester) async {
173 174 175

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

176
    final Key key = GlobalKey();
177

178
    await tester.pumpWidget(
179 180 181 182 183 184 185 186 187 188 189 190 191 192
      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);
193
                  },
194
                ),
195
              ),
196 197 198 199 200 201 202 203 204 205 206
            ),
          ),
          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);
207
                },
208 209 210 211
              ),
            ),
          ),
        ),
212
      ),
213 214 215 216 217
    );

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

218
    await tester.pump();
219 220 221 222 223

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

    flipStatefulWidget(tester);
224
    await tester.pump();
225 226 227 228 229

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

    flipStatefulWidget(tester);
230
    await tester.pump();
231 232 233 234 235

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

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

239
    final Key key = GlobalKey();
240

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

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

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

277
    await tester.pump();
278 279 280 281 282

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

    flipStatefulWidget(tester);
283
    await tester.pump();
284 285 286 287 288

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

    flipStatefulWidget(tester);
289
    await tester.pump();
290 291 292 293 294

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

295
  testWidgets('Update inherited when removing node and child has global key with constant child, minimised', (WidgetTester tester) async {
296 297 298

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

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

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

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

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

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

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

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

    flipStatefulWidget(tester);
336
    await tester.pump();
337 338 339 340 341

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

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

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

356
    await tester.pumpWidget(
357
      inner,
358 359 360 361
    );
    expect(inheritedValue, isNull);

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

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

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

384
    await tester.pumpWidget(
385
      inner,
386 387 388
    );
    expect(buildCount, equals(1));

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

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

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

415
    await tester.pumpWidget(
416
      inner,
417 418 419
    );
    expect(buildCount, equals(1));

420
    await tester.pumpWidget(
421
      ValueInherited(
422
        value: 3,
423
        child: inner,
424
      ),
425 426 427
    );
    expect(buildCount, equals(2));
  });
428 429 430 431 432

  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;

433
    final TestInherited parent = TestInherited(child: ExpectFail(() {
434 435 436 437 438 439
      exceptionCaught = true;
    }));
    await tester.pumpWidget(parent);

    expect(exceptionCaught, isTrue);
  });
440 441 442 443 444 445 446

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

    final Widget builder = Builder(
      builder: (BuildContext context) {
447
        context.dependOnInheritedWidgetOfExactType<ChangeNotifierInherited>();
448 449
        buildCount += 1;
        return Container();
450
      },
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
    );

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

466
    notifier.notifyListeners();
467 468 469 470 471 472 473 474 475 476 477
    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));
  });
478
}