// Copyright 2016 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 'dart:async'; import 'package:flutter_tools/src/base/context.dart'; import 'package:test/test.dart'; void main() { group('AppContext', () { group('global getter', () { bool called; setUp(() { called = false; }); test('returns non-null context in the root zone', () { expect(context, isNotNull); }); test('returns root context in child of root zone if zone was manually created', () { final Zone rootZone = Zone.current; final AppContext rootContext = context; runZoned(() { expect(Zone.current, isNot(rootZone)); expect(Zone.current.parent, rootZone); expect(context, rootContext); called = true; }); expect(called, isTrue); }); test('returns child context after run', () async { final AppContext rootContext = context; await rootContext.run(name: 'child', body: () { expect(context, isNot(rootContext)); expect(context.name, 'child'); called = true; }); expect(called, isTrue); }); test('returns grandchild context after nested run', () async { final AppContext rootContext = context; await rootContext.run(name: 'child', body: () async { final AppContext childContext = context; await childContext.run(name: 'grandchild', body: () { expect(context, isNot(rootContext)); expect(context, isNot(childContext)); expect(context.name, 'grandchild'); called = true; }); }); expect(called, isTrue); }); test('scans up zone hierarchy for first context', () async { final AppContext rootContext = context; await rootContext.run(name: 'child', body: () { final AppContext childContext = context; runZoned(() { expect(context, isNot(rootContext)); expect(context, same(childContext)); expect(context.name, 'child'); called = true; }); }); expect(called, isTrue); }); }); group('operator[]', () { test('still finds values if async code runs after body has finished', () async { final Completer outer = new Completer(); final Completer inner = new Completer(); String value; await context.run( body: () { outer.future.then((_) { value = context[String]; inner.complete(); }); }, fallbacks: { String: () => 'value', }, ); expect(value, isNull); outer.complete(); await inner.future; expect(value, 'value'); }); test('caches generated override values', () async { int consultationCount = 0; String value; await context.run( body: () async { final StringBuffer buf = new StringBuffer(context[String]); buf.write(context[String]); await context.run(body: () { buf.write(context[String]); }); value = buf.toString(); }, overrides: { String: () { consultationCount++; return 'v'; }, }, ); expect(value, 'vvv'); expect(consultationCount, 1); }); test('caches generated fallback values', () async { int consultationCount = 0; String value; await context.run( body: () async { final StringBuffer buf = new StringBuffer(context[String]); buf.write(context[String]); await context.run(body: () { buf.write(context[String]); }); value = buf.toString(); }, fallbacks: { String: () { consultationCount++; return 'v'; }, }, ); expect(value, 'vvv'); expect(consultationCount, 1); }); test('returns null if generated value is null', () async { final String value = await context.run( body: () => context[String], overrides: { String: () => null, }, ); expect(value, isNull); }); test('throws if generator has dependency cycle', () async { final Future value = context.run( body: () async { return context[String]; }, fallbacks: { int: () => int.parse(context[String]), String: () => '${context[double]}', double: () => context[int] * 1.0, }, ); try { await value; fail('ContextDependencyCycleException expected but not thrown.'); } on ContextDependencyCycleException catch (e) { expect(e.cycle, [String, double, int]); expect(e.toString(), 'Dependency cycle detected: String -> double -> int'); } }); }); group('run', () { test('returns the value returned by body', () async { expect(await context.run(body: () => 123), 123); expect(await context.run(body: () => 'value'), 'value'); expect(await context.run>(body: () async => 456), 456); }); test('passes name to child context', () async { await context.run(name: 'child', body: () { expect(context.name, 'child'); }); }); group('fallbacks', () { bool called; setUp(() { called = false; }); test('are applied after parent context is consulted', () async { final String value = await context.run( body: () { return context.run( body: () { called = true; return context[String]; }, fallbacks: { String: () => 'child', }, ); }, ); expect(called, isTrue); expect(value, 'child'); }); test('are not applied if parent context supplies value', () async { bool childConsulted = false; final String value = await context.run( body: () { return context.run( body: () { called = true; return context[String]; }, fallbacks: { String: () { childConsulted = true; return 'child'; }, }, ); }, fallbacks: { String: () => 'parent', }, ); expect(called, isTrue); expect(value, 'parent'); expect(childConsulted, isFalse); }); test('may depend on one another', () async { final String value = await context.run( body: () { return context[String]; }, fallbacks: { int: () => 123, String: () => '-${context[int]}-', }, ); expect(value, '-123-'); }); }); group('overrides', () { test('intercept consultation of parent context', () async { bool parentConsulted = false; final String value = await context.run( body: () { return context.run( body: () => context[String], overrides: { String: () => 'child', }, ); }, fallbacks: { String: () { parentConsulted = true; return 'parent'; }, }, ); expect(value, 'child'); expect(parentConsulted, isFalse); }); }); }); }); }