// 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 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('Element diagnostics json includes widgetRuntimeType', () async { final Element element = _TestElement(); final Map json = element.toDiagnosticsNode().toJsonMap( const DiagnosticsSerializationDelegate(), ); expect(json['widgetRuntimeType'], 'Placeholder'); expect(json['stateful'], isFalse); }); test('StatefulElement diagnostics are stateful', () { final Element element = StatefulElement(const Tooltip(message: 'foo')); final Map json = element.toDiagnosticsNode().toJsonMap( const DiagnosticsSerializationDelegate(), ); expect(json['widgetRuntimeType'], 'Tooltip'); expect(json['stateful'], isTrue); }); group('Serialization', () { // These are always included. const defaultDiagnosticKeys = ['description']; // These are only included when fullDetails = false. const essentialDiagnosticKeys = ['shouldIndent']; // These are only included with fullDetails = true. const detailedDiagnosticKeys = ['type', 'hasChildren', 'allowWrap']; final testTree = 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), DiagnosticsProperty('DO_NOT_SHOW_NULL', null, defaultValue: null), DiagnosticsProperty('nullProperty', null), StringProperty('node_type', '', showName: false, quoted: false), ], children: [ TestTree(name: 'node A'), TestTree( name: 'node B', properties: [ StringProperty('p1', 'v1', quoted: false), StringProperty('p2', 'v2', quoted: false), ], children: [ TestTree(name: 'node B1'), TestTree( name: 'node B2', properties: [StringProperty('property1', 'value1', quoted: false)], ), TestTree( name: 'node B3', properties: [ StringProperty('node_type', '', showName: false, quoted: false), IntProperty('foo', 42), ], ), ], ), TestTree( name: 'node C', properties: [ StringProperty('foo', 'multi\nline\nvalue!', quoted: false), ], ), ], ); test('default', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( const DiagnosticsSerializationDelegate(), ); expect(result.containsKey('properties'), isFalse); expect(result.containsKey('children'), isFalse); for (final keyName in defaultDiagnosticKeys) { expect(result.containsKey(keyName), isTrue, reason: '$keyName is included.'); } for (final keyName in detailedDiagnosticKeys) { expect(result.containsKey(keyName), isTrue, reason: '$keyName is included.'); } }); test('iterative implementation (without full details)', () { final Map result = testTree.toDiagnosticsNode().toJsonMapIterative( const DiagnosticsSerializationDelegate(), ); expect(result.containsKey('properties'), isFalse); expect(result.containsKey('children'), isFalse); for (final keyName in defaultDiagnosticKeys) { expect(result.containsKey(keyName), isTrue, reason: '$keyName is included.'); } for (final keyName in essentialDiagnosticKeys) { expect(result.containsKey(keyName), isTrue, reason: '$keyName is included.'); } for (final keyName in detailedDiagnosticKeys) { expect(result.containsKey(keyName), isFalse, reason: '$keyName is not included.'); } // The truncated value should not be included if it is false. expect(result['truncated'] == null || result['truncated'] == true, isTrue); }); test('subtreeDepth 1', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( const DiagnosticsSerializationDelegate(subtreeDepth: 1), ); expect(result.containsKey('properties'), isFalse); final children = result['children']! as List>; expect(children[0].containsKey('children'), isFalse); expect(children[1].containsKey('children'), isFalse); expect(children[2].containsKey('children'), isFalse); }); test('subtreeDepth 5', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( const DiagnosticsSerializationDelegate(subtreeDepth: 5), ); expect(result.containsKey('properties'), isFalse); final children = result['children']! as List>; expect(children[0]['children'], hasLength(0)); expect(children[1]['children'], hasLength(3)); expect(children[2]['children'], hasLength(0)); }); test('includeProperties', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( const DiagnosticsSerializationDelegate(includeProperties: true), ); expect(result.containsKey('children'), isFalse); expect(result['properties'], hasLength(7)); }); test('includeProperties with subtreedepth 1', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( const DiagnosticsSerializationDelegate(includeProperties: true, subtreeDepth: 1), ); expect(result['properties'], hasLength(7)); final children = result['children']! as List>; expect(children, hasLength(3)); expect(children[0]['properties'], hasLength(0)); expect(children[1]['properties'], hasLength(2)); expect(children[2]['properties'], hasLength(1)); }); test('additionalNodeProperties', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( const TestDiagnosticsSerializationDelegate( includeProperties: true, subtreeDepth: 1, additionalNodePropertiesMap: {'foo': true}, ), ); expect(result['foo'], isTrue); final properties = result['properties']! as List>; expect(properties, hasLength(7)); expect(properties.every((Map property) => property['foo'] == true), isTrue); final children = result['children']! as List>; expect(children, hasLength(3)); expect(children.every((Map child) => child['foo'] == true), isTrue); }); test('filterProperties - sublist', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( TestDiagnosticsSerializationDelegate( includeProperties: true, propertyFilter: (List nodes, DiagnosticsNode owner) { return nodes.whereType().toList(); }, ), ); final properties = result['properties']! as List>; expect(properties, hasLength(3)); expect( properties.every((Map property) => property['type'] == 'StringProperty'), isTrue, ); }); test('filterProperties - replace', () { var replaced = false; final Map result = testTree.toDiagnosticsNode().toJsonMap( TestDiagnosticsSerializationDelegate( includeProperties: true, propertyFilter: (List nodes, DiagnosticsNode owner) { if (replaced) { return nodes; } replaced = true; return [StringProperty('foo', 'bar')]; }, ), ); final properties = result['properties']! as List>; expect(properties, hasLength(1)); expect(properties.single['name'], 'foo'); }); test('filterChildren - sublist', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( TestDiagnosticsSerializationDelegate( subtreeDepth: 1, childFilter: (List nodes, DiagnosticsNode owner) { return nodes.where((DiagnosticsNode node) => node.getProperties().isEmpty).toList(); }, ), ); final children = result['children']! as List>; expect(children, hasLength(1)); }); test('filterChildren - replace', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( TestDiagnosticsSerializationDelegate( subtreeDepth: 1, childFilter: (List nodes, DiagnosticsNode owner) { return nodes.expand((DiagnosticsNode node) => node.getChildren()).toList(); }, ), ); final children = result['children']! as List>; expect(children, hasLength(3)); expect(children.first['name'], 'child node B1'); }); test('nodeTruncator', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( TestDiagnosticsSerializationDelegate( subtreeDepth: 5, includeProperties: true, nodeTruncator: (List nodes, DiagnosticsNode? owner) { return nodes.take(2).toList(); }, ), ); final children = result['children']! as List>; expect(children, hasLength(3)); expect(children.last['truncated'], isTrue); final properties = result['properties']! as List>; expect(properties, hasLength(3)); expect(properties.last['truncated'], isTrue); }); test('delegateForAddingNodes', () { final Map result = testTree.toDiagnosticsNode().toJsonMap( TestDiagnosticsSerializationDelegate( subtreeDepth: 5, includeProperties: true, nodeDelegator: (DiagnosticsNode node, DiagnosticsSerializationDelegate delegate) { return delegate.copyWith(includeProperties: false); }, ), ); final properties = result['properties']! as List>; expect(properties, hasLength(7)); expect( properties.every((Map property) => !property.containsKey('properties')), isTrue, ); final children = result['children']! as List>; expect(children, hasLength(3)); expect( children.every((Map child) => !child.containsKey('properties')), isTrue, ); }); }); } class _TestElement extends Element { _TestElement() : super(const Placeholder()); @override bool get debugDoingBuild => throw UnimplementedError(); } 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); } } typedef NodeDelegator = DiagnosticsSerializationDelegate Function( DiagnosticsNode node, TestDiagnosticsSerializationDelegate delegate, ); typedef NodeTruncator = List Function(List nodes, DiagnosticsNode? owner); typedef NodeFilter = List Function(List nodes, DiagnosticsNode owner); class TestDiagnosticsSerializationDelegate implements DiagnosticsSerializationDelegate { const TestDiagnosticsSerializationDelegate({ this.includeProperties = false, this.subtreeDepth = 0, this.additionalNodePropertiesMap = const {}, this.childFilter, this.propertyFilter, this.nodeTruncator, this.nodeDelegator, }); final Map additionalNodePropertiesMap; final NodeFilter? childFilter; final NodeFilter? propertyFilter; final NodeTruncator? nodeTruncator; final NodeDelegator? nodeDelegator; @override Map additionalNodeProperties(DiagnosticsNode node, {bool fullDetails = true}) { return additionalNodePropertiesMap; } @override DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node) { if (nodeDelegator != null) { return nodeDelegator!(node, this); } return subtreeDepth > 0 ? copyWith(subtreeDepth: subtreeDepth - 1) : this; } @override bool get expandPropertyValues => false; @override List filterChildren(List nodes, DiagnosticsNode owner) { return childFilter?.call(nodes, owner) ?? nodes; } @override List filterProperties(List nodes, DiagnosticsNode owner) { return propertyFilter?.call(nodes, owner) ?? nodes; } @override final bool includeProperties; @override final int subtreeDepth; @override List truncateNodesList(List nodes, DiagnosticsNode? owner) { return nodeTruncator?.call(nodes, owner) ?? nodes; } @override DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties}) { return TestDiagnosticsSerializationDelegate( includeProperties: includeProperties ?? this.includeProperties, subtreeDepth: subtreeDepth ?? this.subtreeDepth, additionalNodePropertiesMap: additionalNodePropertiesMap, childFilter: childFilter, propertyFilter: propertyFilter, nodeTruncator: nodeTruncator, nodeDelegator: nodeDelegator, ); } }