context_test.dart 8.59 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 7
import 'dart:async';

import 'package:flutter_tools/src/base/context.dart';
8

9
import '../../src/common.dart';
10

11
void main() {
12
  group('AppContext', () {
13
    group('global getter', () {
14
      late bool called;
15

16 17
      setUp(() {
        called = false;
18
      });
19

20 21 22
      test('returns non-null context in the root zone', () {
        expect(context, isNotNull);
      });
23

24 25 26
      test('returns root context in child of root zone if zone was manually created', () {
        final Zone rootZone = Zone.current;
        final AppContext rootContext = context;
27
        runZoned<void>(() {
28 29 30 31 32 33 34
          expect(Zone.current, isNot(rootZone));
          expect(Zone.current.parent, rootZone);
          expect(context, rootContext);
          called = true;
        });
        expect(called, isTrue);
      });
35

36
      test('returns child context after run', () async {
37
        final AppContext rootContext = context;
38
        await rootContext.run<void>(name: 'child', body: () {
39 40 41 42 43
          expect(context, isNot(rootContext));
          expect(context.name, 'child');
          called = true;
        });
        expect(called, isTrue);
44
      });
45

46
      test('returns grandchild context after nested run', () async {
47
        final AppContext rootContext = context;
48
        await rootContext.run<void>(name: 'child', body: () async {
49
          final AppContext childContext = context;
50
          await childContext.run<void>(name: 'grandchild', body: () {
51 52 53 54 55 56 57 58 59
            expect(context, isNot(rootContext));
            expect(context, isNot(childContext));
            expect(context.name, 'grandchild');
            called = true;
          });
        });
        expect(called, isTrue);
      });

60
      test('scans up zone hierarchy for first context', () async {
61
        final AppContext rootContext = context;
62
        await rootContext.run<void>(name: 'child', body: () {
63
          final AppContext childContext = context;
64
          runZoned<void>(() {
65 66 67 68 69 70 71 72
            expect(context, isNot(rootContext));
            expect(context, same(childContext));
            expect(context.name, 'child');
            called = true;
          });
        });
        expect(called, isTrue);
      });
73 74
    });

75 76
    group('operator[]', () {
      test('still finds values if async code runs after body has finished', () async {
77 78
        final Completer<void> outer = Completer<void>();
        final Completer<void> inner = Completer<void>();
79
        String? value;
80
        await context.run<void>(
81
          body: () {
82
            outer.future.then<void>((_) {
83
              value = context.get<String>();
84 85 86 87 88 89 90 91 92 93 94 95 96
              inner.complete();
            });
          },
          fallbacks: <Type, Generator>{
            String: () => 'value',
          },
        );
        expect(value, isNull);
        outer.complete();
        await inner.future;
        expect(value, 'value');
      });

97
      test('caches generated override values', () async {
98
        int consultationCount = 0;
99
        String? value;
100 101
        await context.run<void>(
          body: () async {
102
            final StringBuffer buf = StringBuffer(context.get<String>()!);
103
            buf.write(context.get<String>());
104
            await context.run<void>(body: () {
105
              buf.write(context.get<String>());
106 107 108 109 110 111 112 113 114 115 116 117 118
            });
            value = buf.toString();
          },
          overrides: <Type, Generator>{
            String: () {
              consultationCount++;
              return 'v';
            },
          },
        );
        expect(value, 'vvv');
        expect(consultationCount, 1);
      });
119

120
      test('caches generated fallback values', () async {
121
        int consultationCount = 0;
122
        String? value;
123 124
        await context.run(
          body: () async {
125
            final StringBuffer buf = StringBuffer(context.get<String>()!);
126
            buf.write(context.get<String>());
127
            await context.run<void>(body: () {
128
              buf.write(context.get<String>());
129 130 131 132 133 134 135 136 137 138 139 140
            });
            value = buf.toString();
          },
          fallbacks: <Type, Generator>{
            String: () {
              consultationCount++;
              return 'v';
            },
          },
        );
        expect(value, 'vvv');
        expect(consultationCount, 1);
141
      });
142

143
      test('returns null if generated value is null', () async {
144
        final String? value = await context.run<String?>(
145
          body: () => context.get<String>(),
146 147 148 149 150 151 152 153
          overrides: <Type, Generator>{
            String: () => null,
          },
        );
        expect(value, isNull);
      });

      test('throws if generator has dependency cycle', () async {
154
        final Future<String?> value = context.run<String?>(
155
          body: () async {
156
            return context.get<String>();
157 158
          },
          fallbacks: <Type, Generator>{
159
            int: () => int.parse(context.get<String>() ?? ''),
160
            String: () => '${context.get<double>()}',
161
            double: () => context.get<int>()! * 1.0,
162 163
          },
        );
164 165 166 167 168 169 170 171 172 173 174 175
        expect(
          () => value,
          throwsA(
            isA<ContextDependencyCycleException>()
              .having((ContextDependencyCycleException error) => error.cycle, 'cycle', <Type>[String, double, int])
              .having(
                (ContextDependencyCycleException error) => error.toString(),
                'toString()',
                'Dependency cycle detected: String -> double -> int',
              ),
          ),
        );
176
      });
177
    });
178

179 180
    group('run', () {
      test('returns the value returned by body', () async {
181 182
        expect(await context.run<int>(body: () => 123), 123);
        expect(await context.run<String>(body: () => 'value'), 'value');
183
        expect(await context.run<int>(body: () async => 456), 456);
184 185
      });

186 187
      test('passes name to child context', () async {
        await context.run<void>(name: 'child', body: () {
188
          expect(context.name, 'child');
189 190 191
        });
      });

192
      group('fallbacks', () {
193
        late bool called;
194 195 196 197 198

        setUp(() {
          called = false;
        });

199
        test('are applied after parent context is consulted', () async {
200
          final String? value = await context.run<String?>(
201
            body: () {
202
              return context.run<String?>(
203 204
                body: () {
                  called = true;
205
                  return context.get<String>();
206 207 208 209 210 211 212 213 214 215 216
                },
                fallbacks: <Type, Generator>{
                  String: () => 'child',
                },
              );
            },
          );
          expect(called, isTrue);
          expect(value, 'child');
        });

217
        test('are not applied if parent context supplies value', () async {
218
          bool childConsulted = false;
219
          final String? value = await context.run<String?>(
220
            body: () {
221
              return context.run<String?>(
222 223
                body: () {
                  called = true;
224
                  return context.get<String>();
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
                },
                fallbacks: <Type, Generator>{
                  String: () {
                    childConsulted = true;
                    return 'child';
                  },
                },
              );
            },
            fallbacks: <Type, Generator>{
              String: () => 'parent',
            },
          );
          expect(called, isTrue);
          expect(value, 'parent');
          expect(childConsulted, isFalse);
        });

243
        test('may depend on one another', () async {
244
          final String? value = await context.run<String?>(
245
            body: () {
246
              return context.get<String>();
247 248 249
            },
            fallbacks: <Type, Generator>{
              int: () => 123,
250
              String: () => '-${context.get<int>()}-',
251 252 253
            },
          );
          expect(value, '-123-');
254 255
        });
      });
256

257
      group('overrides', () {
258
        test('intercept consultation of parent context', () async {
259
          bool parentConsulted = false;
260
          final String? value = await context.run<String?>(
261
            body: () {
262
              return context.run<String?>(
263
                body: () => context.get<String>(),
264 265 266 267 268 269 270 271 272 273 274 275 276 277
                overrides: <Type, Generator>{
                  String: () => 'child',
                },
              );
            },
            fallbacks: <Type, Generator>{
              String: () {
                parentConsulted = true;
                return 'parent';
              },
            },
          );
          expect(value, 'child');
          expect(parentConsulted, isFalse);
278 279 280
        });
      });
    });
281 282
  });
}