| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file is hand-formatted. |
| |
| import 'dart:io' show Platform; |
| import 'dart:ui' as ui; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:rfw/formats.dart' show parseLibraryFile; |
| import 'package:rfw/rfw.dart'; |
| |
| final bool masterChannel = |
| !Platform.environment.containsKey('CHANNEL') || |
| Platform.environment['CHANNEL'] == 'master'; |
| |
| // See Contributing section of README.md file. |
| final bool runGoldens = Platform.isLinux && masterChannel; |
| |
| void main() { |
| testWidgets('String example', (WidgetTester tester) async { |
| Duration? duration; |
| Curve? curve; |
| int buildCount = 0; |
| final Widget builder = Builder( |
| builder: (BuildContext context) { |
| buildCount += 1; |
| duration = AnimationDefaults.durationOf(context); |
| curve = AnimationDefaults.curveOf(context); |
| return const SizedBox.shrink(); |
| }, |
| ); |
| await tester.pumpWidget( |
| AnimationDefaults( |
| duration: const Duration(milliseconds: 500), |
| curve: Curves.easeIn, |
| child: builder, |
| ), |
| ); |
| expect(duration, const Duration(milliseconds: 500)); |
| expect(curve, Curves.easeIn); |
| expect(buildCount, 1); |
| await tester.pumpWidget( |
| AnimationDefaults( |
| duration: const Duration(milliseconds: 500), |
| curve: Curves.easeIn, |
| child: builder, |
| ), |
| ); |
| expect(buildCount, 1); |
| await tester.pumpWidget( |
| AnimationDefaults( |
| duration: const Duration(milliseconds: 501), |
| curve: Curves.easeIn, |
| child: builder, |
| ), |
| ); |
| expect(buildCount, 2); |
| }); |
| |
| testWidgets('spot checks', (WidgetTester tester) async { |
| Duration? duration; |
| Curve? curve; |
| int buildCount = 0; |
| final Runtime runtime = Runtime() |
| ..update(const LibraryName(<String>['core']), createCoreWidgets()) |
| ..update(const LibraryName(<String>['builder']), LocalWidgetLibrary(<String, LocalWidgetBuilder>{ |
| 'Test': (BuildContext context, DataSource source) { |
| buildCount += 1; |
| duration = AnimationDefaults.durationOf(context); |
| curve = AnimationDefaults.curveOf(context); |
| return const SizedBox.shrink(); |
| }, |
| })) |
| ..update(const LibraryName(<String>['test']), parseLibraryFile('import core; widget root = SizedBox();')); |
| final DynamicContent data = DynamicContent(); |
| final List<String> eventLog = <String>[]; |
| await tester.pumpWidget( |
| RemoteWidget( |
| runtime: runtime, |
| data: data, |
| widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'), |
| onEvent: (String eventName, DynamicMap eventArguments) { |
| eventLog.add(eventName); |
| expect(eventArguments, const <String, Object?>{ 'argument': true }); |
| }, |
| ), |
| ); |
| expect(find.byType(SizedBox), findsOneWidget); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = Align(alignment: { x: 0.25, y: 0.75 }); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<Align>(find.byType(Align)).alignment, const Alignment(0.25, 0.75)); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = Align(alignment: { start: 0.25, y: 0.75 }); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<Align>(find.byType(Align)).alignment, const Alignment(0.25, 0.75)); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| import builder; |
| widget root = AnimationDefaults(curve: "easeOut", duration: 5000, child: Test()); |
| ''')); |
| await tester.pump(); |
| expect(buildCount, 1); |
| expect(duration, const Duration(seconds: 5)); |
| expect(curve, Curves.easeOut); |
| |
| ArgumentDecoders.curveDecoders['saw3'] = (DataSource source, List<Object> key) => const SawTooth(3); |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| import builder; |
| widget root = AnimationDefaults(curve: "saw3", child: Test()); |
| ''')); |
| await tester.pump(); |
| expect(curve, isA<SawTooth>()); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = AspectRatio(aspectRatio: 0.5); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<AspectRatio>(find.byType(AspectRatio)).aspectRatio, 0.5); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = Center(widthFactor: 0.25); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<Center>(find.byType(Center)).widthFactor, 0.25); |
| expect(tester.widget<Center>(find.byType(Center)).heightFactor, null); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = ColoredBox(color: 0xFF112233); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF112233)); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = Column( |
| mainAxisAlignment: "center", |
| children: [ ColoredBox(color: 1), ColoredBox(color: 2) ], |
| ); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<Column>(find.byType(Column)).mainAxisAlignment, MainAxisAlignment.center); |
| expect(tester.widget<Column>(find.byType(Column)).crossAxisAlignment, CrossAxisAlignment.center); |
| expect(tester.widget<Column>(find.byType(Column)).verticalDirection, VerticalDirection.down); |
| expect(tester.widget<Column>(find.byType(Column)).children, hasLength(2)); |
| expect(tester.widgetList<ColoredBox>(find.byType(ColoredBox)).toList()[0].color, const Color(0x00000001)); |
| expect(tester.widgetList<ColoredBox>(find.byType(ColoredBox)).toList()[1].color, const Color(0x00000002)); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = ColoredBox(color: 0xFF112233); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF112233)); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = DefaultTextStyle( |
| textHeightBehavior: { applyHeightToLastDescent: false }, |
| child: SizedBoxShrink(), |
| ); |
| ''')); |
| await tester.pump(); |
| expect( |
| tester.widget<DefaultTextStyle>(find.byType(DefaultTextStyle)).textHeightBehavior, |
| const TextHeightBehavior(applyHeightToLastDescent: false), |
| ); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = Directionality( |
| textDirection: "ltr", |
| child: SizedBoxShrink(), |
| ); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<Directionality>(find.byType(Directionality)).textDirection, TextDirection.ltr); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = FittedBox( |
| fit: "cover", |
| ); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<FittedBox>(find.byType(FittedBox)).fit, BoxFit.cover); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = GestureDetector( |
| onTap: event 'tap' { argument: true }, |
| child: ColoredBox(), |
| ); |
| ''')); |
| await tester.pump(); |
| await tester.tap(find.byType(ColoredBox)); |
| expect(eventLog, <String>['tap']); |
| eventLog.clear(); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = Directionality( |
| textDirection: "ltr", |
| child: Icon( |
| icon: 0x0001, |
| fontFamily: 'FONT', |
| ), |
| ); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<Icon>(find.byType(Icon)).icon!.codePoint, 1); |
| expect(tester.widget<Icon>(find.byType(Icon)).icon!.fontFamily, 'FONT'); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = IconTheme( |
| color: 0x12345678, |
| child: SizedBoxShrink(), |
| ); |
| ''')); |
| await tester.pump(); |
| expect(tester.widget<IconTheme>(find.byType(IconTheme)).data.color, const Color(0x12345678)); |
| }); |
| |
| testWidgets('golden checks', (WidgetTester tester) async { |
| final Runtime runtime = Runtime() |
| ..update(const LibraryName(<String>['core']), createCoreWidgets()) |
| ..update(const LibraryName(<String>['test']), parseLibraryFile('import core; widget root = SizedBox();')); |
| final DynamicContent data = DynamicContent(); |
| final List<String> eventLog = <String>[]; |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.rtl, |
| child: RemoteWidget( |
| runtime: runtime, |
| data: data, |
| widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'), |
| onEvent: (String eventName, DynamicMap eventArguments) { |
| eventLog.add('$eventName $eventArguments'); |
| }, |
| ), |
| ), |
| ); |
| expect(find.byType(RemoteWidget), findsOneWidget); |
| |
| ArgumentDecoders.decorationDecoders['tab'] = (DataSource source, List<Object> key) { |
| return UnderlineTabIndicator( |
| borderSide: ArgumentDecoders.borderSide(source, <Object>[...key, 'side']) ?? const BorderSide(width: 2.0, color: Color(0xFFFFFFFF)), |
| insets: ArgumentDecoders.edgeInsets(source, <Object>['insets']) ?? EdgeInsets.zero, |
| ); |
| }; |
| ArgumentDecoders.gradientDecoders['custom'] = (DataSource source, List<Object> key) { |
| return const RadialGradient( |
| center: Alignment(0.7, -0.6), |
| radius: 0.2, |
| colors: <Color>[ Color(0xFFFFFF00), Color(0xFF0099FF) ], |
| stops: <double>[0.4, 1.0], |
| ); |
| }; |
| ArgumentDecoders.shapeBorderDecoders['custom'] = (DataSource source, List<Object> key) { |
| return StarBorder( |
| side: ArgumentDecoders.borderSide(source, <Object>[...key, 'side']) ?? const BorderSide(width: 2.0, color: Color(0xFFFFFFFF)), |
| points: source.v<double>(<Object>[...key, 'points']) ?? 5.0, |
| ); |
| }; |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = Container( |
| margin: [20.0, 10.0, 30.0, 5.0], |
| padding: [10.0], |
| decoration: { |
| type: 'box', |
| borderRadius: [ { x: 120.0 }, { x: 130.0, y: 40.0 } ], |
| image: { |
| // this image doesn't exist so nothing much happens here |
| // we check the results of this parse in a separate expect |
| source: 'asset', |
| color: 0xFF00BBCC, |
| centerSlice: { x: 5.0, y: 8.0, w: 100.0, h: 70.0 }, |
| colorFilter: { |
| type: 'matrix', matrix: [ |
| 1.0, 1.0, 1.0, 1.0, 1.0, |
| 1.0, 1.0, 1.0, 1.0, 1.0, |
| 1.0, 1.0, 1.0, 1.0, 1.0, |
| 1.0, 1.0, 1.0, 1.0, 1.0, |
| ], |
| }, |
| }, |
| gradient: { |
| type: 'sweep', |
| }, |
| }, |
| foregroundDecoration: { |
| type: 'box', |
| border: [ { width: 10.0, color: 0xFFFFFF00 }, { width: 3.0, color: 0xFF00FFFF } ], |
| boxShadow: [ { offset: { x: 25.0, y: 25.0 }, color: 0x5F000000, } ], |
| image: { |
| // this image also doesn't exist |
| // we check the results of this parse in a separate expect |
| source: 'x-invalid://', |
| colorFilter: { |
| type: 'mode', |
| color: 0xFF8811FF, |
| blendMode: "xor", |
| }, |
| onError: event 'image-error-event' { }, |
| }, |
| gradient: { |
| type: 'linear', |
| colors: [ 0x1F009900, 0x1F33CC33, 0x7F777700 ], |
| stops: [ 0.0, 0.75, 1.0 ], |
| }, |
| }, |
| alignment: { x: 0.0, y: -0.5, }, |
| transform: [ |
| 0.9, 0.2, 0.1, 0.0, |
| -0.1, 1.1, 0.0, 0.0, |
| 0.0, 0.0, 1.0, 0.0, |
| 50.0, -20.0, 0.0, 1.0, |
| ], |
| child: Container( |
| constraints: { maxWidth: 400.0, maxHeight: 350.0 }, |
| margin: [5.0, 25.0, 10.0, 20.0], |
| decoration: { |
| type: 'box', |
| color: 0xFF9911CC, |
| gradient: { type: 'custom' }, |
| }, |
| foregroundDecoration: { |
| type: 'flutterLogo', |
| margin: [ 100.0 ], |
| }, |
| child: Container( |
| margin: [5.0], |
| decoration: { |
| type: 'tab', |
| side: { width: 20.0, color: 0xFFFFFFFF }, |
| }, |
| foregroundDecoration: { |
| type: 'shape', |
| shape: [ |
| { type: 'box', border: { width: 10.0, color: 0xFF0000FF } }, |
| { type: 'beveled', borderRadius: [ { x: 60.0 } ], side: { width: 10.0, color: 0xFF0033FF } }, |
| { type: 'circle', side: { width: 10.0, color: 0xFF0066FF } }, |
| { type: 'continuous', borderRadius: [ { x: 60.0 }, { x: 80.0 }, { x: 0.0 }, { x: 20.0, y: 50.0 } ], side: { width: 10.0, color: 0xFFEEFF33 } }, |
| { type: 'rounded', borderRadius: [ { x: 20.0 } ], side: { width: 10.0, color: 0xFF00CCFF } }, |
| { type: 'stadium', side: { width: 10.0, color: 0xFF00FFFF } }, |
| { type: 'custom', side: { width: 5.0, color: 0xFFFFFF00 }, points: 6 }, // star |
| ], |
| gradient: { |
| type: 'radial', |
| }, |
| }, |
| ), |
| ), |
| ); |
| ''')); |
| await tester.pump(); |
| expect(eventLog, hasLength(1)); |
| expect(eventLog.first, startsWith('image-error-event {exception: HTTP request failed, statusCode: 400, x-invalid:')); |
| eventLog.clear(); |
| await expectLater( |
| find.byType(RemoteWidget), |
| matchesGoldenFile('goldens/argument_decoders_test.containers.png'), |
| skip: !runGoldens, |
| ); |
| expect(find.byType(DecoratedBox), findsNWidgets(6)); |
| expect( |
| (tester.widgetList<DecoratedBox>(find.byType(DecoratedBox)).toList()[1].decoration as BoxDecoration).image.toString(), |
| 'DecorationImage(AssetImage(bundle: null, name: "asset"), ' // this just seemed like the easiest way to check all this... |
| 'ColorFilter.matrix([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]), ' |
| 'Alignment.center, centerSlice: Rect.fromLTRB(5.0, 8.0, 105.0, 78.0), scale 1.0, opacity 1.0, FilterQuality.low)', |
| ); |
| expect( |
| (tester.widgetList<DecoratedBox>(find.byType(DecoratedBox)).toList()[0].decoration as BoxDecoration).image.toString(), |
| 'DecorationImage(NetworkImage("x-invalid://", scale: 1.0), ' |
| 'ColorFilter.mode(Color(0xff8811ff), BlendMode.xor), Alignment.center, scale 1.0, ' |
| 'opacity 1.0, FilterQuality.low)', |
| ); |
| |
| ArgumentDecoders.colorFilterDecoders['custom'] = (DataSource source, List<Object> key) { |
| return const ColorFilter.mode(Color(0x12345678), BlendMode.xor); |
| }; |
| ArgumentDecoders.maskFilterDecoders['custom'] = (DataSource source, List<Object> key) { |
| return const MaskFilter.blur(BlurStyle.outer, 0.5); |
| }; |
| ArgumentDecoders.shaderDecoders['custom'] = (DataSource source, List<Object> key) { |
| return ui.Gradient.linear(Offset.zero, const Offset(100.0, 100.0), const <Color>[Color(0xFFFFFF00), Color(0xFF00FFFF)]); |
| }; |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = Column( |
| children: [ |
| Text( |
| text: [ |
| 'Hello World Hello World Hello World Hello World Hello World Hello World Hello World', |
| 'Hello World Hello World Hello World Hello World Hello World Hello World Hello World', |
| ], |
| locale: "en-US", |
| style: { |
| fontFamilyFallback: [ "a", "b" ], |
| fontSize: 30.0, |
| }, |
| strutStyle: { |
| fontSize: 50.0, |
| }, |
| ), |
| Expanded( |
| flex: 2, |
| child: Text( |
| text: 'Aaaa Aaaaaaa Aaaaa', |
| locale: "en", |
| style: { |
| decoration: [ "underline", "overline" ], |
| decorationColor: 0xFF00FF00, |
| fontFeatures: [ { feature: 'sups' } ], |
| foreground: { |
| blendMode: 'color', |
| color: 0xFFEEDDCC, |
| colorFilter: { type: 'srgbToLinearGamma' }, |
| filterQuality: "high", |
| isAntiAlias: true, |
| maskFilter: { type: 'blur' }, |
| shader: { type: 'linear', rect: { x: 0.0, y: 0.0, w: 300.0, h: 200.0, } } |
| }, |
| background: { |
| colorFilter: { type: 'custom' }, |
| maskFilter: { type: 'custom' }, |
| shader: { type: 'custom' }, |
| }, |
| }, |
| ), |
| ), |
| Expanded( |
| flex: 1, |
| child: Text( |
| text: 'B', |
| locale: "en-latin-GB", |
| ), |
| ), |
| ], |
| ); |
| ''')); |
| await tester.pump(); |
| expect(tester.firstWidget<Text>(find.byType(Text)).style!.fontFamilyFallback, <String>[ 'a', 'b' ]); |
| expect(tester.widgetList<Text>(find.byType(Text)).map<Locale>((Text widget) => widget.locale!), const <Locale>[Locale('en', 'US'), Locale('en'), Locale.fromSubtags(languageCode: 'en', scriptCode: 'latin', countryCode: 'GB')]); |
| await expectLater( |
| find.byType(RemoteWidget), |
| matchesGoldenFile('goldens/argument_decoders_test.text.png'), |
| skip: !runGoldens, |
| ); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = GridView( |
| gridDelegate: { type: 'fixedCrossAxisCount', crossAxisCount: 3 }, |
| children: [ |
| ColoredBox(color: 0xFF118844), |
| ColoredBox(color: 0xFFEE8844), |
| ColoredBox(color: 0xFF882244), |
| ColoredBox(color: 0xFF449999), |
| ColoredBox(color: 0xFF330088), |
| ColoredBox(color: 0xFF8822CC), |
| ColoredBox(color: 0xFF330000), |
| ColoredBox(color: 0xFF992288), |
| ], |
| ); |
| ''')); |
| await tester.pump(); |
| await expectLater( |
| find.byType(RemoteWidget), |
| matchesGoldenFile('goldens/argument_decoders_test.gridview.fixed.png'), |
| skip: !runGoldens, |
| ); |
| |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = GridView( |
| gridDelegate: { type: 'maxCrossAxisExtent', maxCrossAxisExtent: 50.0 }, |
| children: [ |
| ColoredBox(color: 0xFF118844), |
| ColoredBox(color: 0xFFEE8844), |
| ColoredBox(color: 0xFF882244), |
| ColoredBox(color: 0xFF449999), |
| ColoredBox(color: 0xFF330088), |
| ColoredBox(color: 0xFF8822CC), |
| ColoredBox(color: 0xFF330000), |
| ColoredBox(color: 0xFF992288), |
| ], |
| ); |
| ''')); |
| await tester.pump(); |
| await expectLater( |
| find.byType(RemoteWidget), |
| matchesGoldenFile('goldens/argument_decoders_test.gridview.max.png'), |
| skip: !runGoldens, |
| ); |
| |
| int sawGridDelegateDecoder = 0; |
| ArgumentDecoders.gridDelegateDecoders['custom'] = (DataSource source, List<Object> key) { |
| sawGridDelegateDecoder += 1; |
| return null; |
| }; |
| runtime.update(const LibraryName(<String>['test']), parseLibraryFile(''' |
| import core; |
| widget root = GridView( |
| gridDelegate: { type: 'custom' }, |
| children: [ |
| ColoredBox(color: 0xFF118844), |
| ColoredBox(color: 0xFFEE8844), |
| ColoredBox(color: 0xFF882244), |
| ColoredBox(color: 0xFF449999), |
| ColoredBox(color: 0xFF330088), |
| ColoredBox(color: 0xFF8822CC), |
| ColoredBox(color: 0xFF330000), |
| ColoredBox(color: 0xFF992288), |
| ], |
| ); |
| ''')); |
| expect(sawGridDelegateDecoder, 0); |
| await tester.pump(); |
| expect(sawGridDelegateDecoder, 1); |
| await expectLater( |
| find.byType(RemoteWidget), |
| matchesGoldenFile('goldens/argument_decoders_test.gridview.custom.png'), |
| skip: !runGoldens, |
| ); |
| |
| expect(eventLog, isEmpty); |
| }, skip: !masterChannel); // https://github.com/flutter/flutter/pull/129851 |
| } |