// Copyright 2014 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. 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 (final 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()))) as Map; } 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'] ?? DiagnosticLevel.info.name, equals(node.level.name)); expect(json['showName'] ?? true, equals(node.showName)); expect(json['emptyBodyDescription'], equals(node.emptyBodyDescription)); expect(json['style'] ?? DiagnosticsTreeStyle.sparse.name, equals(node.style!.name)); 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']! as List; 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:\n' ' │ 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('toString test', () { final TestTree tree = TestTree( properties: [ StringProperty('stringProperty1', 'value1', quoted: false), DoubleProperty('doubleProperty1', 42.5), DoubleProperty('roundedProperty', 1.0 / 3.0), StringProperty('DO_NOT_SHOW', 'DO_NOT_SHOW', level: DiagnosticLevel.hidden, quoted: false), StringProperty('DEBUG_ONLY', 'DEBUG_ONLY', level: DiagnosticLevel.debug, quoted: false), ], // child to verify that children are not included in the toString. children: [TestTree(name: 'node A')], ); expect( tree.toString(), equalsIgnoringHashCodes('TestTree#00000(stringProperty1: value1, doubleProperty1: 42.5, roundedProperty: 0.3)'), ); expect( tree.toString(minLevel: DiagnosticLevel.debug), equalsIgnoringHashCodes('TestTree#00000(stringProperty1: value1, doubleProperty1: 42.5, roundedProperty: 0.3, DEBUG_ONLY: DEBUG_ONLY)'), ); }); 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')); expect( () => describeEnum('Hello World'), throwsA(isAssertionError.having( (AssertionError e) => e.message, 'message', 'The provided object "Hello World" is not an enum.', )), ); }); 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', ); expect(quoted.toString(), equals('name: "value"')); validateStringPropertyJsonSerialization(quoted); expect( StringProperty('name', 'value', showName: false).toString(), equals('"value"'), ); expect( StringProperty( 'name', null, showName: false, ).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', () { void 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); // https://github.com/flutter/flutter/issues/54221 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 DiagnosticsProperty 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 DiagnosticsProperty 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 DiagnosticsProperty 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 DiagnosticsProperty 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 DiagnosticsProperty 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 DiagnosticsProperty 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', () { void 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 { void onClick() { } void 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 { void onClick() { } void 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 { void 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 as DiagnosticsProperty); }); test('error message style wrap test', () { // This tests wrapping of properties with styles typical for error messages. DiagnosticsNode createTreeWithWrappingNodes({ DiagnosticsTreeStyle? rootStyle, required 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'); }); test('IntProperty arguments passed to super', () { final DiagnosticsProperty property = IntProperty( 'Example', 0, ifNull: 'is null', showName: false, defaultValue: 1, style: DiagnosticsTreeStyle.none, level: DiagnosticLevel.off, ); expect(property.value, equals(0)); expect(property.ifNull, equals('is null')); expect(property.showName, equals(false)); expect(property.defaultValue, equals(1)); expect(property.style, equals(DiagnosticsTreeStyle.none)); expect(property.level, equals(DiagnosticLevel.off)); }); }