// Copyright 2017 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:convert'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; class TestTree extends Object with DiagnosticableTreeMixin { TestTree({ this.name, this.style, this.children = const [], this.properties = const [], }); final String name; final List children; final List properties; final DiagnosticsTreeStyle style; @override List debugDescribeChildren() => [ for (TestTree child in children) child.toDiagnosticsNode( name: 'child ${child.name}', style: child.style, ), ]; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); if (style != null) properties.defaultDiagnosticsTreeStyle = style; this.properties.forEach(properties.add); } } enum ExampleEnum { hello, world, deferToChild, } /// Encode and decode to JSON to make sure all objects in the JSON for the /// [DiagnosticsNode] are valid JSON. Map simulateJsonSerialization(DiagnosticsNode node) { return json.decode(json.encode(node.toJsonMap(const DiagnosticsSerializationDelegate()))); } void validateNodeJsonSerialization(DiagnosticsNode node) { validateNodeJsonSerializationHelper(simulateJsonSerialization(node), node); } void validateNodeJsonSerializationHelper(Map json, DiagnosticsNode node) { expect(json['name'], equals(node.name)); expect(json['showSeparator'] ?? true, equals(node.showSeparator)); expect(json['description'], equals(node.toDescription())); expect(json['level'] ?? describeEnum(DiagnosticLevel.info), equals(describeEnum(node.level))); expect(json['showName'] ?? true, equals(node.showName)); expect(json['emptyBodyDescription'], equals(node.emptyBodyDescription)); expect(json['style'] ?? describeEnum(DiagnosticsTreeStyle.sparse), equals(describeEnum(node.style))); expect(json['type'], equals(node.runtimeType.toString())); expect(json['hasChildren'] ?? false, equals(node.getChildren().isNotEmpty)); } void validatePropertyJsonSerialization(DiagnosticsProperty property) { validatePropertyJsonSerializationHelper(simulateJsonSerialization(property), property); } void validateStringPropertyJsonSerialization(StringProperty property) { final Map json = simulateJsonSerialization(property); expect(json['quoted'], equals(property.quoted)); validatePropertyJsonSerializationHelper(json, property); } void validateFlagPropertyJsonSerialization(FlagProperty property) { final Map json = simulateJsonSerialization(property); expect(json['ifTrue'], equals(property.ifTrue)); if (property.ifTrue != null) { expect(json['ifTrue'], equals(property.ifTrue)); } else { expect(json.containsKey('ifTrue'), isFalse); } if (property.ifFalse != null) { expect(json['ifFalse'], property.ifFalse); } else { expect(json.containsKey('isFalse'), isFalse); } validatePropertyJsonSerializationHelper(json, property); } void validateDoublePropertyJsonSerialization(DoubleProperty property) { final Map json = simulateJsonSerialization(property); if (property.unit != null) { expect(json['unit'], equals(property.unit)); } else { expect(json.containsKey('unit'), isFalse); } expect(json['numberToString'], equals(property.numberToString())); validatePropertyJsonSerializationHelper(json, property); } void validateObjectFlagPropertyJsonSerialization(ObjectFlagProperty property) { final Map json = simulateJsonSerialization(property); if (property.ifPresent != null) { expect(json['ifPresent'], equals(property.ifPresent)); } else { expect(json.containsKey('ifPresent'), isFalse); } validatePropertyJsonSerializationHelper(json, property); } void validateIterableFlagsPropertyJsonSerialization(FlagsSummary property) { final Map json = simulateJsonSerialization(property); if (property.value.isNotEmpty) { expect(json['values'], equals( property.value.entries .where((MapEntry entry) => entry.value != null) .map((MapEntry entry) => entry.key).toList(), )); } else { expect(json.containsKey('values'), isFalse); } validatePropertyJsonSerializationHelper(json, property); } void validateIterablePropertyJsonSerialization(IterableProperty property) { final Map json = simulateJsonSerialization(property); if (property.value != null) { final List valuesJson = json['values']; final List expectedValues = property.value.map((Object value) => value.toString()).toList(); expect(listEquals(valuesJson, expectedValues), isTrue); } else { expect(json.containsKey('values'), isFalse); } validatePropertyJsonSerializationHelper(json, property); } void validatePropertyJsonSerializationHelper(final Map json, DiagnosticsProperty property) { if (property.defaultValue != kNoDefaultValue) { expect(json['defaultValue'], equals(property.defaultValue.toString())); } else { expect(json.containsKey('defaultValue'), isFalse); } if (property.ifEmpty != null) { expect(json['ifEmpty'], equals(property.ifEmpty)); } else { expect(json.containsKey('ifEmpty'), isFalse); } if (property.ifNull != null) { expect(json['ifNull'], equals(property.ifNull)); } else { expect(json.containsKey('ifNull'), isFalse); } if (property.tooltip != null) { expect(json['tooltip'], equals(property.tooltip)); } else { expect(json.containsKey('tooltip'), isFalse); } expect(json['missingIfNull'], equals(property.missingIfNull)); if (property.exception != null) { expect(json['exception'], equals(property.exception.toString())); } else { expect(json.containsKey('exception'), isFalse); } expect(json['propertyType'], equals(property.propertyType.toString())); expect(json.containsKey('defaultLevel'), isTrue); if (property.value is Diagnosticable) { expect(json['isDiagnosticableValue'], isTrue); } else { expect(json.containsKey('isDiagnosticableValue'), isFalse); } validateNodeJsonSerializationHelper(json, property); } void main() { test('TreeDiagnosticsMixin control test', () async { void goldenStyleTest( String description, { DiagnosticsTreeStyle style, DiagnosticsTreeStyle lastChildStyle, String golden = '', }) { final TestTree tree = TestTree(children: [ TestTree(name: 'node A', style: style), TestTree( name: 'node B', children: [ TestTree(name: 'node B1', style: style), TestTree(name: 'node B2', style: style), TestTree(name: 'node B3', style: lastChildStyle ?? style), ], style: style, ), TestTree(name: 'node C', style: lastChildStyle ?? style), ], style: lastChildStyle); expect(tree, hasAGoodToStringDeep); expect( tree.toDiagnosticsNode(style: style).toStringDeep(), equalsIgnoringHashCodes(golden), reason: description, ); validateNodeJsonSerialization(tree.toDiagnosticsNode()); } goldenStyleTest( 'dense', style: DiagnosticsTreeStyle.dense, golden: 'TestTree#00000\n' '├child node A: TestTree#00000\n' '├child node B: TestTree#00000\n' '│├child node B1: TestTree#00000\n' '│├child node B2: TestTree#00000\n' '│└child node B3: TestTree#00000\n' '└child node C: TestTree#00000\n', ); goldenStyleTest( 'sparse', style: DiagnosticsTreeStyle.sparse, golden: 'TestTree#00000\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ └─child node B3: TestTree#00000\n' ' └─child node C: TestTree#00000\n', ); goldenStyleTest( 'dashed', style: DiagnosticsTreeStyle.offstage, golden: 'TestTree#00000\n' ' ╎╌child node A: TestTree#00000\n' ' ╎╌child node B: TestTree#00000\n' ' ╎ ╎╌child node B1: TestTree#00000\n' ' ╎ ╎╌child node B2: TestTree#00000\n' ' ╎ └╌child node B3: TestTree#00000\n' ' └╌child node C: TestTree#00000\n', ); goldenStyleTest( 'leaf children', style: DiagnosticsTreeStyle.sparse, lastChildStyle: DiagnosticsTreeStyle.transition, golden: 'TestTree#00000\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ ╘═╦══ child node B3 ═══\n' ' │ ║ TestTree#00000\n' ' │ ╚═══════════\n' ' ╘═╦══ child node C ═══\n' ' ║ TestTree#00000\n' ' ╚═══════════\n', ); goldenStyleTest( 'error children', style: DiagnosticsTreeStyle.sparse, lastChildStyle: DiagnosticsTreeStyle.error, golden: 'TestTree#00000\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ ╘═╦══╡ CHILD NODE B3: TESTTREE#00000 ╞═══════════════════════════════\n' ' │ ╚══════════════════════════════════════════════════════════════════\n' ' ╘═╦══╡ CHILD NODE C: TESTTREE#00000 ╞════════════════════════════════\n' ' ╚══════════════════════════════════════════════════════════════════\n', ); // You would never really want to make everything a transition child like // this but you can and still get a readable tree. // The joint between single and double lines here is a bit clunky // but we could correct that if there is any real use for this style. goldenStyleTest( 'transition', style: DiagnosticsTreeStyle.transition, golden: 'TestTree#00000:\n' ' ╞═╦══ child node A ═══\n' ' │ ║ TestTree#00000\n' ' │ ╚═══════════\n' ' ╞═╦══ child node B ═══\n' ' │ ║ TestTree#00000:\n' ' │ ║ ╞═╦══ child node B1 ═══\n' ' │ ║ │ ║ TestTree#00000\n' ' │ ║ │ ╚═══════════\n' ' │ ║ ╞═╦══ child node B2 ═══\n' ' │ ║ │ ║ TestTree#00000\n' ' │ ║ │ ╚═══════════\n' ' │ ║ ╘═╦══ child node B3 ═══\n' ' │ ║ ║ TestTree#00000\n' ' │ ║ ╚═══════════\n' ' │ ╚═══════════\n' ' ╘═╦══ child node C ═══\n' ' ║ TestTree#00000\n' ' ╚═══════════\n', ); goldenStyleTest( 'error', style: DiagnosticsTreeStyle.error, golden: '══╡ TESTTREE#00000 ╞═════════════════════════════════════════════\n' '╞═╦══╡ CHILD NODE A: TESTTREE#00000 ╞════════════════════════════════\n' '│ ╚══════════════════════════════════════════════════════════════════\n' '╞═╦══╡ CHILD NODE B: TESTTREE#00000 ╞════════════════════════════════\n' '│ ║ ╞═╦══╡ CHILD NODE B1: TESTTREE#00000 ╞═══════════════════════════════\n' '│ ║ │ ╚══════════════════════════════════════════════════════════════════\n' '│ ║ ╞═╦══╡ CHILD NODE B2: TESTTREE#00000 ╞═══════════════════════════════\n' '│ ║ │ ╚══════════════════════════════════════════════════════════════════\n' '│ ║ ╘═╦══╡ CHILD NODE B3: TESTTREE#00000 ╞═══════════════════════════════\n' '│ ║ ╚══════════════════════════════════════════════════════════════════\n' '│ ╚══════════════════════════════════════════════════════════════════\n' '╘═╦══╡ CHILD NODE C: TESTTREE#00000 ╞════════════════════════════════\n' ' ╚══════════════════════════════════════════════════════════════════\n' '═════════════════════════════════════════════════════════════════\n', ); goldenStyleTest( 'whitespace', style: DiagnosticsTreeStyle.whitespace, golden: 'TestTree#00000:\n' ' child node A: TestTree#00000\n' ' child node B: TestTree#00000:\n' ' child node B1: TestTree#00000\n' ' child node B2: TestTree#00000\n' ' child node B3: TestTree#00000\n' ' child node C: TestTree#00000\n', ); // Single line mode does not display children. goldenStyleTest( 'single line', style: DiagnosticsTreeStyle.singleLine, golden: 'TestTree#00000', ); }); test('TreeDiagnosticsMixin tree with properties test', () async { void goldenStyleTest( String description, { String name, DiagnosticsTreeStyle style, DiagnosticsTreeStyle lastChildStyle, DiagnosticsTreeStyle propertyStyle = DiagnosticsTreeStyle.singleLine, @required String golden, }) { final TestTree tree = TestTree( properties: [ StringProperty('stringProperty1', 'value1', quoted: false, style: propertyStyle), DoubleProperty('doubleProperty1', 42.5, style: propertyStyle), DoubleProperty('roundedProperty', 1.0 / 3.0, style: propertyStyle), StringProperty('DO_NOT_SHOW', 'DO_NOT_SHOW', level: DiagnosticLevel.hidden, quoted: false, style: propertyStyle), DiagnosticsProperty('DO_NOT_SHOW_NULL', null, defaultValue: null, style: propertyStyle), DiagnosticsProperty('nullProperty', null, style: propertyStyle), StringProperty('node_type', '', showName: false, quoted: false, style: propertyStyle), ], children: [ TestTree(name: 'node A', style: style), TestTree( name: 'node B', properties: [ StringProperty('p1', 'v1', quoted: false, style: propertyStyle), StringProperty('p2', 'v2', quoted: false, style: propertyStyle), ], children: [ TestTree(name: 'node B1', style: style), TestTree( name: 'node B2', properties: [StringProperty('property1', 'value1', quoted: false, style: propertyStyle)], style: style, ), TestTree( name: 'node B3', properties: [ StringProperty('node_type', '', showName: false, quoted: false, style: propertyStyle), IntProperty('foo', 42, style: propertyStyle), ], style: lastChildStyle ?? style, ), ], style: style, ), TestTree( name: 'node C', properties: [ StringProperty('foo', 'multi\nline\nvalue!', quoted: false, style: propertyStyle), ], style: lastChildStyle ?? style, ), ], style: lastChildStyle, ); if (tree.style != DiagnosticsTreeStyle.singleLine && tree.style != DiagnosticsTreeStyle.errorProperty) { expect(tree, hasAGoodToStringDeep); } expect( tree.toDiagnosticsNode(name: name, style: style).toStringDeep(), equalsIgnoringHashCodes(golden), reason: description, ); validateNodeJsonSerialization(tree.toDiagnosticsNode()); } goldenStyleTest( 'sparse', style: DiagnosticsTreeStyle.sparse, golden: 'TestTree#00000\n' ' │ stringProperty1: value1\n' ' │ doubleProperty1: 42.5\n' ' │ roundedProperty: 0.3\n' ' │ nullProperty: null\n' ' │ \n' ' │\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ │ p1: v1\n' ' │ │ p2: v2\n' ' │ │\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ │ property1: value1\n' ' │ │\n' ' │ └─child node B3: TestTree#00000\n' ' │ \n' ' │ foo: 42\n' ' │\n' ' └─child node C: TestTree#00000\n' ' foo:\n' ' multi\n' ' line\n' ' value!\n', ); goldenStyleTest( 'sparse with indented single line properties', style: DiagnosticsTreeStyle.sparse, propertyStyle: DiagnosticsTreeStyle.errorProperty, golden: 'TestTree#00000\n' ' │ stringProperty1:\n' ' │ value1\n' ' │ doubleProperty1:\n' ' │ 42.5\n' ' │ roundedProperty:\n' ' │ 0.3\n' ' │ nullProperty:\n' ' │ null\n' ' │ \n' ' │\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ │ p1:\n' ' │ │ v1\n' ' │ │ p2:\n' ' │ │ v2\n' ' │ │\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ │ property1:\n' ' │ │ value1\n' ' │ │\n' ' │ └─child node B3: TestTree#00000\n' ' │ \n' ' │ foo: 42\n' ' │\n' ' └─child node C: TestTree#00000\n' ' foo:\n' ' multi\n' ' line\n' ' value!\n', ); goldenStyleTest( 'dense', style: DiagnosticsTreeStyle.dense, golden: 'TestTree#00000(stringProperty1: value1, doubleProperty1: 42.5, roundedProperty: 0.3, nullProperty: null, )\n' '├child node A: TestTree#00000\n' '├child node B: TestTree#00000(p1: v1, p2: v2)\n' '│├child node B1: TestTree#00000\n' '│├child node B2: TestTree#00000(property1: value1)\n' '│└child node B3: TestTree#00000(, foo: 42)\n' '└child node C: TestTree#00000(foo: multi\\nline\\nvalue!)\n', ); goldenStyleTest( 'dashed', style: DiagnosticsTreeStyle.offstage, golden: 'TestTree#00000\n' ' │ stringProperty1: value1\n' ' │ doubleProperty1: 42.5\n' ' │ roundedProperty: 0.3\n' ' │ nullProperty: null\n' ' │ \n' ' │\n' ' ╎╌child node A: TestTree#00000\n' ' ╎╌child node B: TestTree#00000\n' ' ╎ │ p1: v1\n' ' ╎ │ p2: v2\n' ' ╎ │\n' ' ╎ ╎╌child node B1: TestTree#00000\n' ' ╎ ╎╌child node B2: TestTree#00000\n' ' ╎ ╎ property1: value1\n' ' ╎ ╎\n' ' ╎ └╌child node B3: TestTree#00000\n' ' ╎ \n' ' ╎ foo: 42\n' ' ╎\n' ' └╌child node C: TestTree#00000\n' ' foo:\n' ' multi\n' ' line\n' ' value!\n', ); goldenStyleTest( 'transition children', style: DiagnosticsTreeStyle.sparse, lastChildStyle: DiagnosticsTreeStyle.transition, golden: 'TestTree#00000\n' ' │ stringProperty1: value1\n' ' │ doubleProperty1: 42.5\n' ' │ roundedProperty: 0.3\n' ' │ nullProperty: null\n' ' │ \n' ' │\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ │ p1: v1\n' ' │ │ p2: v2\n' ' │ │\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ │ property1: value1\n' ' │ │\n' ' │ ╘═╦══ child node B3 ═══\n' ' │ ║ TestTree#00000:\n' ' │ ║ \n' ' │ ║ foo: 42\n' ' │ ╚═══════════\n' ' ╘═╦══ child node C ═══\n' ' ║ TestTree#00000:\n' ' ║ foo:\n' ' ║ multi\n' ' ║ line\n' ' ║ value!\n' ' ╚═══════════\n', ); goldenStyleTest( 'error children', style: DiagnosticsTreeStyle.sparse, lastChildStyle: DiagnosticsTreeStyle.error, golden: 'TestTree#00000\n' ' │ stringProperty1: value1\n' ' │ doubleProperty1: 42.5\n' ' │ roundedProperty: 0.3\n' ' │ nullProperty: null\n' ' │ \n' ' │\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ │ p1: v1\n' ' │ │ p2: v2\n' ' │ │\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ │ property1: value1\n' ' │ │\n' ' │ ╘═╦══╡ CHILD NODE B3: TESTTREE#00000 ╞═══════════════════════════════\n' ' │ ║ \n' ' │ ║ foo: 42\n' ' │ ╚══════════════════════════════════════════════════════════════════\n' ' ╘═╦══╡ CHILD NODE C: TESTTREE#00000 ╞════════════════════════════════\n' ' ║ foo:\n' ' ║ multi\n' ' ║ line\n' ' ║ value!\n' ' ╚══════════════════════════════════════════════════════════════════\n', ); // You would never really want to make everything a transition child like // this but you can and still get a readable tree. goldenStyleTest( 'transition', style: DiagnosticsTreeStyle.transition, golden: 'TestTree#00000:\n' ' stringProperty1: value1\n' ' doubleProperty1: 42.5\n' ' roundedProperty: 0.3\n' ' nullProperty: null\n' ' \n' ' ╞═╦══ child node A ═══\n' ' │ ║ TestTree#00000\n' ' │ ╚═══════════\n' ' ╞═╦══ child node B ═══\n' ' │ ║ TestTree#00000:\n' ' │ ║ p1: v1\n' ' │ ║ p2: v2\n' ' │ ║ ╞═╦══ child node B1 ═══\n' ' │ ║ │ ║ TestTree#00000\n' ' │ ║ │ ╚═══════════\n' ' │ ║ ╞═╦══ child node B2 ═══\n' ' │ ║ │ ║ TestTree#00000:\n' ' │ ║ │ ║ property1: value1\n' ' │ ║ │ ╚═══════════\n' ' │ ║ ╘═╦══ child node B3 ═══\n' ' │ ║ ║ TestTree#00000:\n' ' │ ║ ║ \n' ' │ ║ ║ foo: 42\n' ' │ ║ ╚═══════════\n' ' │ ╚═══════════\n' ' ╘═╦══ child node C ═══\n' ' ║ TestTree#00000:\n' ' ║ foo:\n' ' ║ multi\n' ' ║ line\n' ' ║ value!\n' ' ╚═══════════\n', ); goldenStyleTest( 'whitespace', style: DiagnosticsTreeStyle.whitespace, golden: 'TestTree#00000:\n' ' stringProperty1: value1\n' ' doubleProperty1: 42.5\n' ' roundedProperty: 0.3\n' ' nullProperty: null\n' ' \n' ' child node A: TestTree#00000\n' ' child node B: TestTree#00000:\n' ' p1: v1\n' ' p2: v2\n' ' child node B1: TestTree#00000\n' ' child node B2: TestTree#00000:\n' ' property1: value1\n' ' child node B3: TestTree#00000:\n' ' \n' ' foo: 42\n' ' child node C: TestTree#00000:\n' ' foo:\n' ' multi\n' ' line\n' ' value!\n', ); goldenStyleTest( 'flat', style: DiagnosticsTreeStyle.flat, golden: 'TestTree#00000:\n' 'stringProperty1: value1\n' 'doubleProperty1: 42.5\n' 'roundedProperty: 0.3\n' 'nullProperty: null\n' '\n' 'child node A: TestTree#00000\n' 'child node B: TestTree#00000:\n' 'p1: v1\n' 'p2: v2\n' 'child node B1: TestTree#00000\n' 'child node B2: TestTree#00000:\n' 'property1: value1\n' 'child node B3: TestTree#00000:\n' '\n' 'foo: 42\n' 'child node C: TestTree#00000:\n' 'foo:\n' ' multi\n' ' line\n' ' value!\n', ); // Single line mode does not display children. goldenStyleTest( 'single line', style: DiagnosticsTreeStyle.singleLine, golden: 'TestTree#00000(stringProperty1: value1, doubleProperty1: 42.5, roundedProperty: 0.3, nullProperty: null, )', ); goldenStyleTest( 'single line', name: 'some name', style: DiagnosticsTreeStyle.singleLine, golden: 'some name: TestTree#00000(stringProperty1: value1, doubleProperty1: 42.5, roundedProperty: 0.3, nullProperty: null, )', ); // No name so we don't indent. goldenStyleTest( 'indented single line', style: DiagnosticsTreeStyle.errorProperty, golden: 'TestTree#00000(stringProperty1: value1, doubleProperty1: 42.5, roundedProperty: 0.3, nullProperty: null, )\n', ); goldenStyleTest( 'indented single line', name: 'some name', style: DiagnosticsTreeStyle.errorProperty, golden: 'some name:\n' ' TestTree#00000(stringProperty1: value1, doubleProperty1: 42.5, roundedProperty: 0.3, nullProperty: null, )\n', ); // TODO(jacobr): this is an ugly test case. // There isn't anything interesting for this case as the children look the // same with and without children. Only difference is the odd and // undesirable density of B3 being right next to node C. goldenStyleTest( 'single line last child', style: DiagnosticsTreeStyle.sparse, lastChildStyle: DiagnosticsTreeStyle.singleLine, golden: 'TestTree#00000\n' ' │ stringProperty1: value1\n' ' │ doubleProperty1: 42.5\n' ' │ roundedProperty: 0.3\n' ' │ nullProperty: null\n' ' │ \n' ' │\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ │ p1: v1\n' ' │ │ p2: v2\n' ' │ │\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ │ property1: value1\n' ' │ │\n' ' │ └─child node B3: TestTree#00000(, foo: 42)\n' ' └─child node C: TestTree#00000(foo: multi\\nline\\nvalue!)\n', ); // TODO(jacobr): this is an ugly test case. // There isn't anything interesting for this case as the children look the // same with and without children. Only difference is the odd and // undesirable density of B3 being right next to node C. // Typically header lines should not be places as leaves in the tree and // should instead be places in front of other nodes that they // function as a header for. goldenStyleTest( 'indented single line last child', style: DiagnosticsTreeStyle.sparse, lastChildStyle: DiagnosticsTreeStyle.errorProperty, golden: 'TestTree#00000\n' ' │ stringProperty1: value1\n' ' │ doubleProperty1: 42.5\n' ' │ roundedProperty: 0.3\n' ' │ nullProperty: null\n' ' │ \n' ' │\n' ' ├─child node A: TestTree#00000\n' ' ├─child node B: TestTree#00000\n' ' │ │ p1: v1\n' ' │ │ p2: v2\n' ' │ │\n' ' │ ├─child node B1: TestTree#00000\n' ' │ ├─child node B2: TestTree#00000\n' ' │ │ property1: value1\n' ' │ │\n' ' │ └─child node B3:\n' ' │ TestTree#00000(, foo: 42)\n' ' └─child node C:\n' ' TestTree#00000(foo: multi\\nline\\nvalue!)\n', ); }); test('transition test', () { // Test multiple styles integrating together in the same tree due to using // transition to go between styles that would otherwise be incompatible. final TestTree tree = TestTree( style: DiagnosticsTreeStyle.sparse, properties: [ StringProperty('stringProperty1', 'value1'), ], children: [ TestTree( style: DiagnosticsTreeStyle.transition, name: 'node transition', properties: [ StringProperty('p1', 'v1'), TestTree( properties: [ DiagnosticsProperty('survived', true), ], ).toDiagnosticsNode(name: 'tree property', style: DiagnosticsTreeStyle.whitespace), ], children: [ TestTree(name: 'dense child', style: DiagnosticsTreeStyle.dense), TestTree( name: 'dense', properties: [StringProperty('property1', 'value1')], style: DiagnosticsTreeStyle.dense, ), TestTree( name: 'node B3', properties: [ StringProperty('node_type', '', showName: false, quoted: false), IntProperty('foo', 42), ], style: DiagnosticsTreeStyle.dense, ), ], ), TestTree( name: 'node C', properties: [ StringProperty('foo', 'multi\nline\nvalue!', quoted: false), ], style: DiagnosticsTreeStyle.sparse, ), ], ); expect(tree, hasAGoodToStringDeep); expect( tree.toDiagnosticsNode().toStringDeep(), equalsIgnoringHashCodes( 'TestTree#00000\n' ' │ stringProperty1: "value1"\n' ' ╞═╦══ child node transition ═══\n' ' │ ║ TestTree#00000:\n' ' │ ║ p1: "v1"\n' ' │ ║ tree property: TestTree#00000:\n' ' │ ║ survived: true\n' ' │ ║ ├child dense child: TestTree#00000\n' ' │ ║ ├child dense: TestTree#00000(property1: "value1")\n' ' │ ║ └child node B3: TestTree#00000(, foo: 42)\n' ' │ ╚═══════════\n' ' └─child node C: TestTree#00000\n' ' foo:\n' ' multi\n' ' line\n' ' value!\n', ), ); }); test('describeEnum test', () { expect(describeEnum(ExampleEnum.hello), equals('hello')); expect(describeEnum(ExampleEnum.world), equals('world')); expect(describeEnum(ExampleEnum.deferToChild), equals('deferToChild')); }); test('string property test', () { expect( StringProperty('name', 'value', quoted: false).toString(), equals('name: value'), ); final StringProperty stringProperty = StringProperty( 'name', 'value', description: 'VALUE', ifEmpty: '', quoted: false, ); expect(stringProperty.toString(), equals('name: VALUE')); validateStringPropertyJsonSerialization(stringProperty); expect( StringProperty( 'name', 'value', showName: false, ifEmpty: '', quoted: false, ).toString(), equals('value'), ); expect( StringProperty('name', '', ifEmpty: '').toString(), equals('name: '), ); expect( StringProperty( 'name', '', ifEmpty: '', showName: false, ).toString(), equals(''), ); expect(StringProperty('name', null).isFiltered(DiagnosticLevel.info), isFalse); expect(StringProperty('name', 'value', level: DiagnosticLevel.hidden).isFiltered(DiagnosticLevel.info), isTrue); expect(StringProperty('name', null, defaultValue: null).isFiltered(DiagnosticLevel.info), isTrue); final StringProperty quoted = StringProperty( 'name', 'value', quoted: true, ); expect(quoted.toString(), equals('name: "value"')); validateStringPropertyJsonSerialization(quoted); expect( StringProperty('name', 'value', showName: false).toString(), equals('"value"'), ); expect( StringProperty( 'name', null, showName: false, quoted: true, ).toString(), equals('null'), ); }); test('bool property test', () { final DiagnosticsProperty trueProperty = DiagnosticsProperty('name', true); final DiagnosticsProperty falseProperty = DiagnosticsProperty('name', false); expect(trueProperty.toString(), equals('name: true')); expect(trueProperty.isFiltered(DiagnosticLevel.info), isFalse); expect(trueProperty.value, isTrue); expect(falseProperty.toString(), equals('name: false')); expect(falseProperty.value, isFalse); expect(falseProperty.isFiltered(DiagnosticLevel.info), isFalse); validatePropertyJsonSerialization(trueProperty); validatePropertyJsonSerialization(falseProperty); final DiagnosticsProperty truthyProperty = DiagnosticsProperty( 'name', true, description: 'truthy', ); expect( truthyProperty.toString(), equals('name: truthy'), ); validatePropertyJsonSerialization(truthyProperty); expect( DiagnosticsProperty('name', true, showName: false).toString(), equals('true'), ); expect(DiagnosticsProperty('name', null).isFiltered(DiagnosticLevel.info), isFalse); expect(DiagnosticsProperty('name', true, level: DiagnosticLevel.hidden).isFiltered(DiagnosticLevel.info), isTrue); expect(DiagnosticsProperty('name', null, defaultValue: null).isFiltered(DiagnosticLevel.info), isTrue); final DiagnosticsProperty missingBool = DiagnosticsProperty('name', null, ifNull: 'missing'); expect( missingBool.toString(), equals('name: missing'), ); validatePropertyJsonSerialization(missingBool); }); test('flag property test', () { final FlagProperty trueFlag = FlagProperty( 'myFlag', value: true, ifTrue: 'myFlag', ); final FlagProperty falseFlag = FlagProperty( 'myFlag', value: false, ifTrue: 'myFlag', ); expect(trueFlag.toString(), equals('myFlag')); validateFlagPropertyJsonSerialization(trueFlag); validateFlagPropertyJsonSerialization(falseFlag); expect(trueFlag.value, isTrue); expect(falseFlag.value, isFalse); expect(trueFlag.isFiltered(DiagnosticLevel.fine), isFalse); expect(falseFlag.isFiltered(DiagnosticLevel.fine), isTrue); }); test('property with tooltip test', () { final DiagnosticsProperty withTooltip = DiagnosticsProperty( 'name', 'value', tooltip: 'tooltip', ); expect( withTooltip.toString(), equals('name: value (tooltip)'), ); expect(withTooltip.value, equals('value')); expect(withTooltip.isFiltered(DiagnosticLevel.fine), isFalse); validatePropertyJsonSerialization(withTooltip); }); test('double property test', () { final DoubleProperty doubleProperty = DoubleProperty( 'name', 42.0, ); expect(doubleProperty.toString(), equals('name: 42.0')); expect(doubleProperty.isFiltered(DiagnosticLevel.info), isFalse); expect(doubleProperty.value, equals(42.0)); validateDoublePropertyJsonSerialization(doubleProperty); expect(DoubleProperty('name', 1.3333).toString(), equals('name: 1.3')); expect(DoubleProperty('name', null).toString(), equals('name: null')); expect(DoubleProperty('name', null).isFiltered(DiagnosticLevel.info), equals(false)); expect( DoubleProperty('name', null, ifNull: 'missing').toString(), equals('name: missing'), ); final DoubleProperty doubleWithUnit = DoubleProperty('name', 42.0, unit: 'px'); expect(doubleWithUnit.toString(), equals('name: 42.0px')); validateDoublePropertyJsonSerialization(doubleWithUnit); }); test('double.infinity serialization test', () { final DoubleProperty infProperty1 = DoubleProperty('double1', double.infinity); validateDoublePropertyJsonSerialization(infProperty1); expect(infProperty1.toString(), equals('double1: Infinity')); final DoubleProperty infProperty2 = DoubleProperty('double2', double.negativeInfinity); validateDoublePropertyJsonSerialization(infProperty2); expect(infProperty2.toString(), equals('double2: -Infinity')); }); test('unsafe double property test', () { final DoubleProperty safe = DoubleProperty.lazy( 'name', () => 42.0, ); expect(safe.toString(), equals('name: 42.0')); expect(safe.isFiltered(DiagnosticLevel.info), isFalse); expect(safe.value, equals(42.0)); validateDoublePropertyJsonSerialization(safe); expect( DoubleProperty.lazy('name', () => 1.3333).toString(), equals('name: 1.3'), ); expect( DoubleProperty.lazy('name', () => null).toString(), equals('name: null'), ); expect( DoubleProperty.lazy('name', () => null).isFiltered(DiagnosticLevel.info), equals(false), ); final DoubleProperty throwingProperty = DoubleProperty.lazy( 'name', () => throw FlutterError.fromParts([ErrorSummary('Invalid constraints')]), ); // TODO(jacobr): it would be better if throwingProperty.object threw an // exception. expect(throwingProperty.value, isNull); expect(throwingProperty.isFiltered(DiagnosticLevel.info), isFalse); expect( throwingProperty.toString(), equals('name: EXCEPTION (FlutterError)'), ); expect(throwingProperty.level, equals(DiagnosticLevel.error)); validateDoublePropertyJsonSerialization(throwingProperty); }); test('percent property', () { expect( PercentProperty('name', 0.4).toString(), equals('name: 40.0%'), ); final PercentProperty complexPercentProperty = PercentProperty('name', 0.99, unit: 'invisible', tooltip: 'almost transparent'); expect( complexPercentProperty.toString(), equals('name: 99.0% invisible (almost transparent)'), ); validateDoublePropertyJsonSerialization(complexPercentProperty); expect( PercentProperty('name', null, unit: 'invisible', tooltip: '!').toString(), equals('name: null (!)'), ); expect( PercentProperty('name', 0.4).value, 0.4, ); expect( PercentProperty('name', 0.0).toString(), equals('name: 0.0%'), ); expect( PercentProperty('name', -10.0).toString(), equals('name: 0.0%'), ); expect( PercentProperty('name', 1.0).toString(), equals('name: 100.0%'), ); expect( PercentProperty('name', 3.0).toString(), equals('name: 100.0%'), ); expect( PercentProperty('name', null).toString(), equals('name: null'), ); expect( PercentProperty( 'name', null, ifNull: 'missing', ).toString(), equals('name: missing'), ); expect( PercentProperty( 'name', null, ifNull: 'missing', showName: false, ).toString(), equals('missing'), ); expect( PercentProperty( 'name', 0.5, showName: false, ).toString(), equals('50.0%'), ); }); test('callback property test', () { final Function onClick = () { }; final ObjectFlagProperty present = ObjectFlagProperty( 'onClick', onClick, ifPresent: 'clickable', ); final ObjectFlagProperty missing = ObjectFlagProperty( 'onClick', null, ifPresent: 'clickable', ); expect(present.toString(), equals('clickable')); expect(present.isFiltered(DiagnosticLevel.info), isFalse); expect(present.value, equals(onClick)); validateObjectFlagPropertyJsonSerialization(present); expect(missing.toString(), equals('onClick: null')); expect(missing.isFiltered(DiagnosticLevel.fine), isTrue); validateObjectFlagPropertyJsonSerialization(missing); }); test('missing callback property test', () { void onClick() { } final ObjectFlagProperty present = ObjectFlagProperty( 'onClick', onClick, ifNull: 'disabled', ); final ObjectFlagProperty missing = ObjectFlagProperty( 'onClick', null, ifNull: 'disabled', ); expect(present.toString(), equals('onClick: Closure: () => void')); expect(present.isFiltered(DiagnosticLevel.fine), isTrue); expect(present.value, equals(onClick)); expect(missing.toString(), equals('disabled')); expect(missing.isFiltered(DiagnosticLevel.info), isFalse); validateObjectFlagPropertyJsonSerialization(present); validateObjectFlagPropertyJsonSerialization(missing); }, skip: isBrowser); test('describe bool property', () { final FlagProperty yes = FlagProperty( 'name', value: true, ifTrue: 'YES', ifFalse: 'NO', showName: true, ); final FlagProperty no = FlagProperty( 'name', value: false, ifTrue: 'YES', ifFalse: 'NO', showName: true, ); expect(yes.toString(), equals('name: YES')); expect(yes.level, equals(DiagnosticLevel.info)); expect(yes.value, isTrue); validateFlagPropertyJsonSerialization(yes); expect(no.toString(), equals('name: NO')); expect(no.level, equals(DiagnosticLevel.info)); expect(no.value, isFalse); validateFlagPropertyJsonSerialization(no); expect( FlagProperty( 'name', value: true, ifTrue: 'YES', ifFalse: 'NO', ).toString(), equals('YES'), ); expect( FlagProperty( 'name', value: false, ifTrue: 'YES', ifFalse: 'NO', ).toString(), equals('NO'), ); expect( FlagProperty( 'name', value: true, ifTrue: 'YES', ifFalse: 'NO', level: DiagnosticLevel.hidden, showName: true, ).level, equals(DiagnosticLevel.hidden), ); }); test('enum property test', () { final EnumProperty hello = EnumProperty( 'name', ExampleEnum.hello, ); final EnumProperty world = EnumProperty( 'name', ExampleEnum.world, ); final EnumProperty deferToChild = EnumProperty( 'name', ExampleEnum.deferToChild, ); final EnumProperty nullEnum = EnumProperty( 'name', null, ); expect(hello.level, equals(DiagnosticLevel.info)); expect(hello.value, equals(ExampleEnum.hello)); expect(hello.toString(), equals('name: hello')); validatePropertyJsonSerialization(hello); expect(world.level, equals(DiagnosticLevel.info)); expect(world.value, equals(ExampleEnum.world)); expect(world.toString(), equals('name: world')); validatePropertyJsonSerialization(world); expect(deferToChild.level, equals(DiagnosticLevel.info)); expect(deferToChild.value, equals(ExampleEnum.deferToChild)); expect(deferToChild.toString(), equals('name: deferToChild')); validatePropertyJsonSerialization(deferToChild); expect(nullEnum.level, equals(DiagnosticLevel.info)); expect(nullEnum.value, isNull); expect(nullEnum.toString(), equals('name: null')); validatePropertyJsonSerialization(nullEnum); final EnumProperty matchesDefault = EnumProperty( 'name', ExampleEnum.hello, defaultValue: ExampleEnum.hello, ); expect(matchesDefault.toString(), equals('name: hello')); expect(matchesDefault.value, equals(ExampleEnum.hello)); expect(matchesDefault.isFiltered(DiagnosticLevel.info), isTrue); validatePropertyJsonSerialization(matchesDefault); expect( EnumProperty( 'name', ExampleEnum.hello, level: DiagnosticLevel.hidden, ).level, equals(DiagnosticLevel.hidden), ); }); test('int property test', () { final IntProperty regular = IntProperty( 'name', 42, ); expect(regular.toString(), equals('name: 42')); expect(regular.value, equals(42)); expect(regular.level, equals(DiagnosticLevel.info)); final IntProperty nullValue = IntProperty( 'name', null, ); expect(nullValue.toString(), equals('name: null')); expect(nullValue.value, isNull); expect(nullValue.level, equals(DiagnosticLevel.info)); final IntProperty hideNull = IntProperty( 'name', null, defaultValue: null, ); expect(hideNull.toString(), equals('name: null')); expect(hideNull.value, isNull); expect(hideNull.isFiltered(DiagnosticLevel.info), isTrue); final IntProperty nullDescription = IntProperty( 'name', null, ifNull: 'missing', ); expect(nullDescription.toString(), equals('name: missing')); expect(nullDescription.value, isNull); expect(nullDescription.level, equals(DiagnosticLevel.info)); final IntProperty hideName = IntProperty( 'name', 42, showName: false, ); expect(hideName.toString(), equals('42')); expect(hideName.value, equals(42)); expect(hideName.level, equals(DiagnosticLevel.info)); final IntProperty withUnit = IntProperty( 'name', 42, unit: 'pt', ); expect(withUnit.toString(), equals('name: 42pt')); expect(withUnit.value, equals(42)); expect(withUnit.level, equals(DiagnosticLevel.info)); final IntProperty defaultValue = IntProperty( 'name', 42, defaultValue: 42, ); expect(defaultValue.toString(), equals('name: 42')); expect(defaultValue.value, equals(42)); expect(defaultValue.isFiltered(DiagnosticLevel.info), isTrue); final IntProperty notDefaultValue = IntProperty( 'name', 43, defaultValue: 42, ); expect(notDefaultValue.toString(), equals('name: 43')); expect(notDefaultValue.value, equals(43)); expect(notDefaultValue.level, equals(DiagnosticLevel.info)); final IntProperty hidden = IntProperty( 'name', 42, level: DiagnosticLevel.hidden, ); expect(hidden.toString(), equals('name: 42')); expect(hidden.value, equals(42)); expect(hidden.level, equals(DiagnosticLevel.hidden)); }); test('object property test', () { const Rect rect = Rect.fromLTRB(0.0, 0.0, 20.0, 20.0); final DiagnosticsNode simple = DiagnosticsProperty( 'name', rect, ); expect(simple.value, equals(rect)); expect(simple.level, equals(DiagnosticLevel.info)); expect(simple.toString(), equals('name: Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)')); validatePropertyJsonSerialization(simple); final DiagnosticsNode withDescription = DiagnosticsProperty( 'name', rect, description: 'small rect', ); expect(withDescription.value, equals(rect)); expect(withDescription.level, equals(DiagnosticLevel.info)); expect(withDescription.toString(), equals('name: small rect')); validatePropertyJsonSerialization(withDescription); final DiagnosticsProperty nullProperty = DiagnosticsProperty( 'name', null, ); expect(nullProperty.value, isNull); expect(nullProperty.level, equals(DiagnosticLevel.info)); expect(nullProperty.toString(), equals('name: null')); validatePropertyJsonSerialization(nullProperty); final DiagnosticsProperty hideNullProperty = DiagnosticsProperty( 'name', null, defaultValue: null, ); expect(hideNullProperty.value, isNull); expect(hideNullProperty.isFiltered(DiagnosticLevel.info), isTrue); expect(hideNullProperty.toString(), equals('name: null')); validatePropertyJsonSerialization(hideNullProperty); final DiagnosticsNode nullDescription = DiagnosticsProperty( 'name', null, ifNull: 'missing', ); expect(nullDescription.value, isNull); expect(nullDescription.level, equals(DiagnosticLevel.info)); expect(nullDescription.toString(), equals('name: missing')); validatePropertyJsonSerialization(nullDescription); final DiagnosticsProperty hideName = DiagnosticsProperty( 'name', rect, showName: false, level: DiagnosticLevel.warning, ); expect(hideName.value, equals(rect)); expect(hideName.level, equals(DiagnosticLevel.warning)); expect(hideName.toString(), equals('Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)')); validatePropertyJsonSerialization(hideName); final DiagnosticsProperty hideSeparator = DiagnosticsProperty( 'Creator', rect, showSeparator: false, ); expect(hideSeparator.value, equals(rect)); expect(hideSeparator.level, equals(DiagnosticLevel.info)); expect( hideSeparator.toString(), equals('Creator Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)'), ); validatePropertyJsonSerialization(hideSeparator); }); test('lazy object property test', () { const Rect rect = Rect.fromLTRB(0.0, 0.0, 20.0, 20.0); final DiagnosticsNode simple = DiagnosticsProperty.lazy( 'name', () => rect, description: 'small rect', ); expect(simple.value, equals(rect)); expect(simple.level, equals(DiagnosticLevel.info)); expect(simple.toString(), equals('name: small rect')); validatePropertyJsonSerialization(simple); final DiagnosticsProperty nullProperty = DiagnosticsProperty.lazy( 'name', () => null, description: 'missing', ); expect(nullProperty.value, isNull); expect(nullProperty.isFiltered(DiagnosticLevel.info), isFalse); expect(nullProperty.toString(), equals('name: missing')); validatePropertyJsonSerialization(nullProperty); final DiagnosticsNode hideNullProperty = DiagnosticsProperty.lazy( 'name', () => null, description: 'missing', defaultValue: null, ); expect(hideNullProperty.value, isNull); expect(hideNullProperty.isFiltered(DiagnosticLevel.info), isTrue); expect(hideNullProperty.toString(), equals('name: missing')); validatePropertyJsonSerialization(hideNullProperty); final DiagnosticsNode hideName = DiagnosticsProperty.lazy( 'name', () => rect, description: 'small rect', showName: false, ); expect(hideName.value, equals(rect)); expect(hideName.isFiltered(DiagnosticLevel.info), isFalse); expect(hideName.toString(), equals('small rect')); validatePropertyJsonSerialization(hideName); final DiagnosticsProperty throwingWithDescription = DiagnosticsProperty.lazy( 'name', () => throw FlutterError.fromParts([ErrorSummary('Property not available')]), description: 'missing', defaultValue: null, ); expect(throwingWithDescription.value, isNull); expect(throwingWithDescription.exception, isFlutterError); expect(throwingWithDescription.isFiltered(DiagnosticLevel.info), false); expect(throwingWithDescription.toString(), equals('name: missing')); validatePropertyJsonSerialization(throwingWithDescription); final DiagnosticsProperty throwingProperty = DiagnosticsProperty.lazy( 'name', () => throw FlutterError.fromParts([ErrorSummary('Property not available')]), defaultValue: null, ); expect(throwingProperty.value, isNull); expect(throwingProperty.exception, isFlutterError); expect(throwingProperty.isFiltered(DiagnosticLevel.info), false); expect(throwingProperty.toString(), equals('name: EXCEPTION (FlutterError)')); validatePropertyJsonSerialization(throwingProperty); }); test('color property test', () { // Add more tests if colorProperty becomes more than a wrapper around // objectProperty. const Color color = Color.fromARGB(255, 255, 255, 255); final DiagnosticsProperty simple = DiagnosticsProperty( 'name', color, ); validatePropertyJsonSerialization(simple); expect(simple.isFiltered(DiagnosticLevel.info), isFalse); expect(simple.value, equals(color)); expect(simple.propertyType, equals(Color)); expect(simple.toString(), equals('name: Color(0xffffffff)')); validatePropertyJsonSerialization(simple); }); test('flag property test', () { final FlagProperty show = FlagProperty( 'wasLayout', value: true, ifTrue: 'layout computed', ); expect(show.name, equals('wasLayout')); expect(show.value, isTrue); expect(show.isFiltered(DiagnosticLevel.info), isFalse); expect(show.toString(), equals('layout computed')); validateFlagPropertyJsonSerialization(show); final FlagProperty hide = FlagProperty( 'wasLayout', value: false, ifTrue: 'layout computed', ); expect(hide.name, equals('wasLayout')); expect(hide.value, isFalse); expect(hide.level, equals(DiagnosticLevel.hidden)); expect(hide.toString(), equals('wasLayout: false')); validateFlagPropertyJsonSerialization(hide); final FlagProperty hideTrue = FlagProperty( 'wasLayout', value: true, ifFalse: 'no layout computed', ); expect(hideTrue.name, equals('wasLayout')); expect(hideTrue.value, isTrue); expect(hideTrue.level, equals(DiagnosticLevel.hidden)); expect(hideTrue.toString(), equals('wasLayout: true')); validateFlagPropertyJsonSerialization(hideTrue); }); test('has property test', () { final Function onClick = () { }; final ObjectFlagProperty has = ObjectFlagProperty.has( 'onClick', onClick, ); expect(has.name, equals('onClick')); expect(has.value, equals(onClick)); expect(has.isFiltered(DiagnosticLevel.info), isFalse); expect(has.toString(), equals('has onClick')); validateObjectFlagPropertyJsonSerialization(has); final ObjectFlagProperty missing = ObjectFlagProperty.has( 'onClick', null, ); expect(missing.name, equals('onClick')); expect(missing.value, isNull); expect(missing.isFiltered(DiagnosticLevel.info), isTrue); expect(missing.toString(), equals('onClick: null')); validateObjectFlagPropertyJsonSerialization(missing); }); test('iterable flags property test', () { // Normal property { final Function onClick = () { }; final Function onMove = () { }; final Map value = { 'click': onClick, 'move': onMove, }; final FlagsSummary flags = FlagsSummary( 'listeners', value, ); expect(flags.name, equals('listeners')); expect(flags.value, equals(value)); expect(flags.isFiltered(DiagnosticLevel.info), isFalse); expect(flags.toString(), equals('listeners: click, move')); validateIterableFlagsPropertyJsonSerialization(flags); } // Reversed-order property { final Function onClick = () { }; final Function onMove = () { }; final Map value = { 'move': onMove, 'click': onClick, }; final FlagsSummary flags = FlagsSummary( 'listeners', value, ); expect(flags.toString(), equals('listeners: move, click')); expect(flags.isFiltered(DiagnosticLevel.info), isFalse); validateIterableFlagsPropertyJsonSerialization(flags); } // Partially empty property { final Function onClick = () { }; final Map value = { 'move': null, 'click': onClick, }; final FlagsSummary flags = FlagsSummary( 'listeners', value, ); expect(flags.toString(), equals('listeners: click')); expect(flags.isFiltered(DiagnosticLevel.info), isFalse); validateIterableFlagsPropertyJsonSerialization(flags); } // Empty property (without ifEmpty) { final Map value = { 'enter': null, }; final FlagsSummary flags = FlagsSummary( 'listeners', value, ); expect(flags.isFiltered(DiagnosticLevel.info), isTrue); validateIterableFlagsPropertyJsonSerialization(flags); } // Empty property (without ifEmpty) { final Map value = { 'enter': null, }; final FlagsSummary flags = FlagsSummary( 'listeners', value, ifEmpty: '', ); expect(flags.toString(), equals('listeners: ')); expect(flags.isFiltered(DiagnosticLevel.info), isFalse); validateIterableFlagsPropertyJsonSerialization(flags); } }); test('iterable property test', () { final List ints = [1,2,3]; final IterableProperty intsProperty = IterableProperty( 'ints', ints, ); expect(intsProperty.value, equals(ints)); expect(intsProperty.isFiltered(DiagnosticLevel.info), isFalse); expect(intsProperty.toString(), equals('ints: 1, 2, 3')); final List doubles = [1,2,3]; final IterableProperty doublesProperty = IterableProperty( 'doubles', doubles, ); expect(doublesProperty.value, equals(doubles)); expect(doublesProperty.isFiltered(DiagnosticLevel.info), isFalse); expect(doublesProperty.toString(), equals('doubles: 1.0, 2.0, 3.0')); final IterableProperty emptyProperty = IterableProperty( 'name', [], ); expect(emptyProperty.value, isEmpty); expect(emptyProperty.isFiltered(DiagnosticLevel.info), isFalse); expect(emptyProperty.toString(), equals('name: []')); validateIterablePropertyJsonSerialization(emptyProperty); final IterableProperty nullProperty = IterableProperty( 'list', null, ); expect(nullProperty.value, isNull); expect(nullProperty.isFiltered(DiagnosticLevel.info), isFalse); expect(nullProperty.toString(), equals('list: null')); validateIterablePropertyJsonSerialization(nullProperty); final IterableProperty hideNullProperty = IterableProperty( 'list', null, defaultValue: null, ); expect(hideNullProperty.value, isNull); expect(hideNullProperty.isFiltered(DiagnosticLevel.info), isTrue); expect(hideNullProperty.level, equals(DiagnosticLevel.fine)); expect(hideNullProperty.toString(), equals('list: null')); validateIterablePropertyJsonSerialization(hideNullProperty); final List objects = [ const Rect.fromLTRB(0.0, 0.0, 20.0, 20.0), const Color.fromARGB(255, 255, 255, 255), ]; final IterableProperty objectsProperty = IterableProperty( 'objects', objects, ); expect(objectsProperty.value, equals(objects)); expect(objectsProperty.isFiltered(DiagnosticLevel.info), isFalse); expect( objectsProperty.toString(), equals('objects: Rect.fromLTRB(0.0, 0.0, 20.0, 20.0), Color(0xffffffff)'), ); validateIterablePropertyJsonSerialization(objectsProperty); final IterableProperty multiLineProperty = IterableProperty( 'objects', objects, style: DiagnosticsTreeStyle.whitespace, ); expect(multiLineProperty.value, equals(objects)); expect(multiLineProperty.isFiltered(DiagnosticLevel.info), isFalse); expect( multiLineProperty.toString(), equals( 'objects:\n' 'Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)\n' 'Color(0xffffffff)', ), ); expect( multiLineProperty.toStringDeep(), equals( 'objects:\n' ' Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)\n' ' Color(0xffffffff)\n', ), ); validateIterablePropertyJsonSerialization(multiLineProperty); expect( TestTree( properties: [multiLineProperty], ).toStringDeep(), equalsIgnoringHashCodes( 'TestTree#00000\n' ' objects:\n' ' Rect.fromLTRB(0.0, 0.0, 20.0, 20.0)\n' ' Color(0xffffffff)\n', ), ); expect( TestTree( properties: [objectsProperty, IntProperty('foo', 42)], style: DiagnosticsTreeStyle.singleLine, ).toStringDeep(), equalsIgnoringHashCodes( 'TestTree#00000(objects: [Rect.fromLTRB(0.0, 0.0, 20.0, 20.0), Color(0xffffffff)], foo: 42)', ), ); // Iterable with a single entry. Verify that rendering is sensible and that // multi line rendering isn't used even though it is not helpful. final List singleElementList = [const Color.fromARGB(255, 255, 255, 255)]; final IterableProperty objectProperty = IterableProperty( 'object', singleElementList, style: DiagnosticsTreeStyle.whitespace, ); expect(objectProperty.value, equals(singleElementList)); expect(objectProperty.isFiltered(DiagnosticLevel.info), isFalse); expect( objectProperty.toString(), equals('object: Color(0xffffffff)'), ); expect( objectProperty.toStringDeep(), equals('object: Color(0xffffffff)\n'), ); validateIterablePropertyJsonSerialization(objectProperty); expect( TestTree( name: 'root', properties: [objectProperty], ).toStringDeep(), equalsIgnoringHashCodes( 'TestTree#00000\n' ' object: Color(0xffffffff)\n', ), ); }); test('Stack trace test', () { final StackTrace stack = StackTrace.fromString( '#0 someMethod() file:///diagnostics_test.dart:42:19\n' '#1 someMethod2() file:///diagnostics_test.dart:12:3\n' '#2 someMethod3() file:///foo.dart:4:1\n' ); expect( DiagnosticsStackTrace('Stack trace', stack).toStringDeep(), equalsIgnoringHashCodes( 'Stack trace:\n' '#0 someMethod() file:///diagnostics_test.dart:42:19\n' '#1 someMethod2() file:///diagnostics_test.dart:12:3\n' '#2 someMethod3() file:///foo.dart:4:1\n' ), ); expect( DiagnosticsStackTrace('-- callback 2 --', stack, showSeparator: false).toStringDeep(), equalsIgnoringHashCodes( '-- callback 2 --\n' '#0 someMethod() file:///diagnostics_test.dart:42:19\n' '#1 someMethod2() file:///diagnostics_test.dart:12:3\n' '#2 someMethod3() file:///foo.dart:4:1\n' ), ); }); test('message test', () { final DiagnosticsNode message = DiagnosticsNode.message('hello world'); expect(message.toString(), equals('hello world')); expect(message.name, isEmpty); expect(message.value, isNull); expect(message.showName, isFalse); validateNodeJsonSerialization(message); final DiagnosticsNode messageProperty = MessageProperty('diagnostics', 'hello world'); expect(messageProperty.toString(), equals('diagnostics: hello world')); expect(messageProperty.name, equals('diagnostics')); expect(messageProperty.value, isNull); expect(messageProperty.showName, isTrue); validatePropertyJsonSerialization(messageProperty); }); test('error message style wrap test', () { // This tests wrapping of properties with styles typical for error messages. DiagnosticsNode createTreeWithWrappingNodes({ DiagnosticsTreeStyle rootStyle, DiagnosticsTreeStyle propertyStyle, }) { return TestTree( name: 'Test tree', properties: [ DiagnosticsNode.message( '--- example property at max length --', style: propertyStyle, ), DiagnosticsNode.message( 'This is a very long message that must wrap as it cannot fit on one line. ' 'This is a very long message that must wrap as it cannot fit on one line. ' 'This is a very long message that must wrap as it cannot fit on one line.', style: propertyStyle, ), DiagnosticsNode.message( '--- example property at max length --', style: propertyStyle, ), DiagnosticsProperty(null, 'Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap.', allowWrap: false), DiagnosticsNode.message( '--- example property at max length --', style: propertyStyle, ), DiagnosticsNode.message( 'This property has a very long property name that will be allowed to wrap unlike most property names. This property has a very long property name that will be allowed to wrap unlike most property names:\n' ' http://someverylongurl.com/that-might-be-tempting-to-wrap-even-though-it-is-a-url-so-should-not-wrap.html', style: propertyStyle, ), DiagnosticsNode.message( 'This property has a very long property name that will be allowed to wrap unlike most property names. This property has a very long property name that will be allowed to wrap unlike most property names:\n' ' https://goo.gl/', style: propertyStyle, ), DiagnosticsNode.message( 'Click on the following url:\n' ' http://someverylongurl.com/that-might-be-tempting-to-wrap-even-though-it-is-a-url-so-should-not-wrap.html', style: propertyStyle, ), DiagnosticsNode.message( 'Click on the following url\n' ' https://goo.gl/', style: propertyStyle, ), DiagnosticsNode.message( '--- example property at max length --', style: propertyStyle, ), DiagnosticsProperty( 'multi-line value', '[1.0, 0.0, 0.0, 0.0]\n' '[1.0, 1.0, 0.0, 0.0]\n' '[1.0, 0.0, 1.0, 0.0]\n' '[1.0, 0.0, 0.0, 1.0]\n', style: propertyStyle, ), DiagnosticsNode.message( '--- example property at max length --', style: propertyStyle, ), DiagnosticsProperty( 'This property has a very long property name that will be allowed to wrap unlike most property names. This property has a very long property name that will be allowed to wrap unlike most property names', 'This is a very long message that must wrap as it cannot fit on one line. ' 'This is a very long message that must wrap as it cannot fit on one line. ' 'This is a very long message that must wrap as it cannot fit on one line.', style: propertyStyle, ), DiagnosticsNode.message( '--- example property at max length --', style: propertyStyle, ), DiagnosticsProperty( 'This property has a very long property name that will be allowed to wrap unlike most property names. This property has a very long property name that will be allowed to wrap unlike most property names', '[1.0, 0.0, 0.0, 0.0]\n' '[1.0, 1.0, 0.0, 0.0]\n' '[1.0, 0.0, 1.0, 0.0]\n' '[1.0, 0.0, 0.0, 1.0]\n', style: propertyStyle, ), DiagnosticsNode.message( '--- example property at max length --', style: propertyStyle, ), MessageProperty( 'diagnosis', 'insufficient data to draw conclusion (less than five repaints)', style: propertyStyle, ), ], ).toDiagnosticsNode(style: rootStyle); } final TextTreeRenderer renderer = TextTreeRenderer(wrapWidth: 40, wrapWidthProperties: 40); expect( renderer.render(createTreeWithWrappingNodes( rootStyle: DiagnosticsTreeStyle.error, propertyStyle: DiagnosticsTreeStyle.singleLine, )), equalsIgnoringHashCodes( '══╡ TESTTREE#00000 ╞════════════════════\n' '--- example property at max length --\n' 'This is a very long message that must\n' 'wrap as it cannot fit on one line. This\n' 'is a very long message that must wrap as\n' 'it cannot fit on one line. This is a\n' 'very long message that must wrap as it\n' 'cannot fit on one line.\n' '--- example property at max length --\n' 'Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap.\n' '--- example property at max length --\n' 'This property has a very long property\n' 'name that will be allowed to wrap unlike\n' 'most property names. This property has a\n' 'very long property name that will be\n' 'allowed to wrap unlike most property\n' 'names:\n' ' http://someverylongurl.com/that-might-be-tempting-to-wrap-even-though-it-is-a-url-so-should-not-wrap.html\n' 'This property has a very long property\n' 'name that will be allowed to wrap unlike\n' 'most property names. This property has a\n' 'very long property name that will be\n' 'allowed to wrap unlike most property\n' 'names:\n' ' https://goo.gl/\n' 'Click on the following url:\n' ' http://someverylongurl.com/that-might-be-tempting-to-wrap-even-though-it-is-a-url-so-should-not-wrap.html\n' 'Click on the following url\n' ' https://goo.gl/\n' '--- example property at max length --\n' 'multi-line value:\n' ' [1.0, 0.0, 0.0, 0.0]\n' ' [1.0, 1.0, 0.0, 0.0]\n' ' [1.0, 0.0, 1.0, 0.0]\n' ' [1.0, 0.0, 0.0, 1.0]\n' '--- example property at max length --\n' 'This property has a very long property\n' 'name that will be allowed to wrap unlike\n' 'most property names. This property has a\n' 'very long property name that will be\n' 'allowed to wrap unlike most property\n' 'names:\n' ' This is a very long message that must\n' ' wrap as it cannot fit on one line.\n' ' This is a very long message that must\n' ' wrap as it cannot fit on one line.\n' ' This is a very long message that must\n' ' wrap as it cannot fit on one line.\n' '--- example property at max length --\n' 'This property has a very long property\n' 'name that will be allowed to wrap unlike\n' 'most property names. This property has a\n' 'very long property name that will be\n' 'allowed to wrap unlike most property\n' 'names:\n' ' [1.0, 0.0, 0.0, 0.0]\n' ' [1.0, 1.0, 0.0, 0.0]\n' ' [1.0, 0.0, 1.0, 0.0]\n' ' [1.0, 0.0, 0.0, 1.0]\n' '--- example property at max length --\n' 'diagnosis: insufficient data to draw\n' ' conclusion (less than five repaints)\n' '════════════════════════════════════════\n', ), ); // This output looks ugly but verifies that no indentation on word wrap // leaks in if the style is flat. expect( renderer.render(createTreeWithWrappingNodes( rootStyle: DiagnosticsTreeStyle.sparse, propertyStyle: DiagnosticsTreeStyle.flat, )), equalsIgnoringHashCodes( 'TestTree#00000\n' ' --- example property at max length --\n' ' This is a very long message that must\n' ' wrap as it cannot fit on one line. This\n' ' is a very long message that must wrap as\n' ' it cannot fit on one line. This is a\n' ' very long message that must wrap as it\n' ' cannot fit on one line.\n' ' --- example property at max length --\n' ' Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap.\n' ' --- example property at max length --\n' ' This property has a very long property\n' ' name that will be allowed to wrap unlike\n' ' most property names. This property has a\n' ' very long property name that will be\n' ' allowed to wrap unlike most property\n' ' names:\n' ' http://someverylongurl.com/that-might-be-tempting-to-wrap-even-though-it-is-a-url-so-should-not-wrap.html\n' ' This property has a very long property\n' ' name that will be allowed to wrap unlike\n' ' most property names. This property has a\n' ' very long property name that will be\n' ' allowed to wrap unlike most property\n' ' names:\n' ' https://goo.gl/\n' ' Click on the following url:\n' ' http://someverylongurl.com/that-might-be-tempting-to-wrap-even-though-it-is-a-url-so-should-not-wrap.html\n' ' Click on the following url\n' ' https://goo.gl/\n' ' --- example property at max length --\n' ' multi-line value:\n' ' [1.0, 0.0, 0.0, 0.0]\n' ' [1.0, 1.0, 0.0, 0.0]\n' ' [1.0, 0.0, 1.0, 0.0]\n' ' [1.0, 0.0, 0.0, 1.0]\n' ' --- example property at max length --\n' ' This property has a very long property\n' ' name that will be allowed to wrap unlike\n' ' most property names. This property has a\n' ' very long property name that will be\n' ' allowed to wrap unlike most property\n' ' names:\n' ' This is a very long message that must\n' ' wrap as it cannot fit on one line. This\n' ' is a very long message that must wrap as\n' ' it cannot fit on one line. This is a\n' ' very long message that must wrap as it\n' ' cannot fit on one line.\n' ' --- example property at max length --\n' ' This property has a very long property\n' ' name that will be allowed to wrap unlike\n' ' most property names. This property has a\n' ' very long property name that will be\n' ' allowed to wrap unlike most property\n' ' names:\n' ' [1.0, 0.0, 0.0, 0.0]\n' ' [1.0, 1.0, 0.0, 0.0]\n' ' [1.0, 0.0, 1.0, 0.0]\n' ' [1.0, 0.0, 0.0, 1.0]\n' ' --- example property at max length --\n' ' diagnosis: insufficient data to draw\n' ' conclusion (less than five repaints)\n' ), ); // This case matches the styles that should generally be used for error // messages expect( renderer.render(createTreeWithWrappingNodes( rootStyle: DiagnosticsTreeStyle.error, propertyStyle: DiagnosticsTreeStyle.errorProperty, )), equalsIgnoringHashCodes( '══╡ TESTTREE#00000 ╞════════════════════\n' '--- example property at max length --\n' 'This is a very long message that must\n' 'wrap as it cannot fit on one line. This\n' 'is a very long message that must wrap as\n' 'it cannot fit on one line. This is a\n' 'very long message that must wrap as it\n' 'cannot fit on one line.\n' '--- example property at max length --\n' 'Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap even though it is very long. Message that is not allowed to wrap.\n' '--- example property at max length --\n' 'This property has a very long property\n' 'name that will be allowed to wrap unlike\n' 'most property names. This property has a\n' 'very long property name that will be\n' 'allowed to wrap unlike most property\n' 'names:\n' ' http://someverylongurl.com/that-might-be-tempting-to-wrap-even-though-it-is-a-url-so-should-not-wrap.html\n' 'This property has a very long property\n' 'name that will be allowed to wrap unlike\n' 'most property names. This property has a\n' 'very long property name that will be\n' 'allowed to wrap unlike most property\n' 'names:\n' ' https://goo.gl/\n' 'Click on the following url:\n' ' http://someverylongurl.com/that-might-be-tempting-to-wrap-even-though-it-is-a-url-so-should-not-wrap.html\n' 'Click on the following url\n' ' https://goo.gl/\n' '--- example property at max length --\n' 'multi-line value:\n' ' [1.0, 0.0, 0.0, 0.0]\n' ' [1.0, 1.0, 0.0, 0.0]\n' ' [1.0, 0.0, 1.0, 0.0]\n' ' [1.0, 0.0, 0.0, 1.0]\n' '--- example property at max length --\n' 'This property has a very long property\n' 'name that will be allowed to wrap unlike\n' 'most property names. This property has a\n' 'very long property name that will be\n' 'allowed to wrap unlike most property\n' 'names:\n' ' This is a very long message that must\n' ' wrap as it cannot fit on one line.\n' ' This is a very long message that must\n' ' wrap as it cannot fit on one line.\n' ' This is a very long message that must\n' ' wrap as it cannot fit on one line.\n' '--- example property at max length --\n' 'This property has a very long property\n' 'name that will be allowed to wrap unlike\n' 'most property names. This property has a\n' 'very long property name that will be\n' 'allowed to wrap unlike most property\n' 'names:\n' ' [1.0, 0.0, 0.0, 0.0]\n' ' [1.0, 1.0, 0.0, 0.0]\n' ' [1.0, 0.0, 1.0, 0.0]\n' ' [1.0, 0.0, 0.0, 1.0]\n' '--- example property at max length --\n' 'diagnosis:\n' ' insufficient data to draw conclusion\n' ' (less than five repaints)\n' '════════════════════════════════════════\n' ), ); }); test('DiagnosticsProperty for basic types has value in json', () { DiagnosticsProperty intProperty = DiagnosticsProperty('int1', 10); Map json = simulateJsonSerialization(intProperty); expect(json['name'], 'int1'); expect(json['value'], 10); intProperty = IntProperty('int2', 20); json = simulateJsonSerialization(intProperty); expect(json['name'], 'int2'); expect(json['value'], 20); DiagnosticsProperty doubleProperty = DiagnosticsProperty('double', 33.3); json = simulateJsonSerialization(doubleProperty); expect(json['name'], 'double'); expect(json['value'], 33.3); doubleProperty = DoubleProperty('double2', 33.3); json = simulateJsonSerialization(doubleProperty); expect(json['name'], 'double2'); expect(json['value'], 33.3); final DiagnosticsProperty boolProperty = DiagnosticsProperty('bool', true); json = simulateJsonSerialization(boolProperty); expect(json['name'], 'bool'); expect(json['value'], true); DiagnosticsProperty stringProperty = DiagnosticsProperty('string1', 'hello'); json = simulateJsonSerialization(stringProperty); expect(json['name'], 'string1'); expect(json['value'], 'hello'); stringProperty = StringProperty('string2', 'world'); json = simulateJsonSerialization(stringProperty); expect(json['name'], 'string2'); expect(json['value'], 'world'); }); }