diagnostics_json_test.dart 13 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13
// 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/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  test('Element diagnostics json includes widgetRuntimeType', () async {
    final Element element = _TestElement();

14
    final Map<String, Object?> json = element.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate());
15 16 17 18
    expect(json['widgetRuntimeType'], 'Placeholder');
    expect(json['stateful'], isFalse);
  });

19
  test('StatefulElement diagnostics are stateful', () {
20 21
    final Element element = StatefulElement(const Tooltip(message: 'foo'));

22
    final Map<String, Object?> json = element.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate());
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
    expect(json['widgetRuntimeType'], 'Tooltip');
    expect(json['stateful'], isTrue);
  });

  group('Serialization', () {
    final TestTree testTree = TestTree(
      properties: <DiagnosticsNode>[
        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<Object>('DO_NOT_SHOW_NULL', null, defaultValue: null),
        DiagnosticsProperty<Object>('nullProperty', null),
        StringProperty('node_type', '<root node>', showName: false, quoted: false),
      ],
      children: <TestTree>[
        TestTree(name: 'node A'),
        TestTree(
          name: 'node B',
          properties: <DiagnosticsNode>[
            StringProperty('p1', 'v1', quoted: false),
            StringProperty('p2', 'v2', quoted: false),
          ],
          children: <TestTree>[
            TestTree(name: 'node B1'),
            TestTree(
              name: 'node B2',
              properties: <DiagnosticsNode>[StringProperty('property1', 'value1', quoted: false)],
            ),
            TestTree(
              name: 'node B3',
              properties: <DiagnosticsNode>[
                StringProperty('node_type', '<leaf node>', showName: false, quoted: false),
                IntProperty('foo', 42),
              ],
            ),
          ],
        ),
        TestTree(
          name: 'node C',
          properties: <DiagnosticsNode>[
            StringProperty('foo', 'multi\nline\nvalue!', quoted: false),
          ],
        ),
      ],
    );

    test('default', () {
71
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate());
72 73 74 75 76
      expect(result.containsKey('properties'), isFalse);
      expect(result.containsKey('children'), isFalse);
    });

    test('subtreeDepth 1', () {
77
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate(subtreeDepth: 1));
78
      expect(result.containsKey('properties'), isFalse);
79
      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
80 81 82 83 84 85
      expect(children[0].containsKey('children'), isFalse);
      expect(children[1].containsKey('children'), isFalse);
      expect(children[2].containsKey('children'), isFalse);
    });

    test('subtreeDepth 5', () {
86
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate(subtreeDepth: 5));
87
      expect(result.containsKey('properties'), isFalse);
88
      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
89 90 91 92 93 94
      expect(children[0]['children'], hasLength(0));
      expect(children[1]['children'], hasLength(3));
      expect(children[2]['children'], hasLength(0));
    });

    test('includeProperties', () {
95
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate(includeProperties: true));
96 97 98 99 100
      expect(result.containsKey('children'), isFalse);
      expect(result['properties'], hasLength(7));
    });

    test('includeProperties with subtreedepth 1', () {
101
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const DiagnosticsSerializationDelegate(
102 103 104 105
        includeProperties: true,
        subtreeDepth: 1,
      ));
      expect(result['properties'], hasLength(7));
106
      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
107 108 109 110 111 112 113
      expect(children, hasLength(3));
      expect(children[0]['properties'], hasLength(0));
      expect(children[1]['properties'], hasLength(2));
      expect(children[2]['properties'], hasLength(1));
    });

    test('additionalNodeProperties', () {
114
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(const TestDiagnosticsSerializationDelegate(
115 116 117 118 119 120 121
        includeProperties: true,
        subtreeDepth: 1,
        additionalNodePropertiesMap: <String, Object>{
          'foo': true,
        },
      ));
      expect(result['foo'], isTrue);
122
      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
123
      expect(properties, hasLength(7));
124
      expect(properties.every((Map<String, Object?> property) => property['foo'] == true), isTrue);
125

126
      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
127
      expect(children, hasLength(3));
128
      expect(children.every((Map<String, Object?> child) => child['foo'] == true), isTrue);
129 130 131
    });

    test('filterProperties - sublist', () {
132
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(TestDiagnosticsSerializationDelegate(
133 134 135
          includeProperties: true,
          propertyFilter: (List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
            return nodes.whereType<StringProperty>().toList();
136
          },
137
      ));
138
      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
139
      expect(properties, hasLength(3));
140
      expect(properties.every((Map<String, Object?> property) => property['type'] == 'StringProperty'), isTrue);
141 142 143 144
    });

    test('filterProperties - replace', () {
      bool replaced = false;
145
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(TestDiagnosticsSerializationDelegate(
146 147 148 149 150 151 152 153 154
          includeProperties: true,
          propertyFilter: (List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
            if (replaced) {
              return nodes;
            }
            replaced = true;
            return <DiagnosticsNode>[
              StringProperty('foo', 'bar'),
            ];
155
          },
156
      ));
157
      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
158 159 160 161 162
      expect(properties, hasLength(1));
      expect(properties.single['name'], 'foo');
    });

    test('filterChildren - sublist', () {
163
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(TestDiagnosticsSerializationDelegate(
164 165 166
          subtreeDepth: 1,
          childFilter: (List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
            return nodes.where((DiagnosticsNode node) => node.getProperties().isEmpty).toList();
167
          },
168
      ));
169
      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
170 171 172 173
      expect(children, hasLength(1));
    });

    test('filterChildren - replace', () {
174
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(TestDiagnosticsSerializationDelegate(
175 176
          subtreeDepth: 1,
          childFilter: (List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
177
            return nodes.expand((DiagnosticsNode node) => node.getChildren()).toList();
178
          },
179
      ));
180
      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
181 182 183 184 185
      expect(children, hasLength(3));
      expect(children.first['name'], 'child node B1');
    });

    test('nodeTruncator', () {
186
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(TestDiagnosticsSerializationDelegate(
187 188
          subtreeDepth: 5,
          includeProperties: true,
189
          nodeTruncator: (List<DiagnosticsNode> nodes, DiagnosticsNode? owner) {
190
            return nodes.take(2).toList();
191
          },
192
      ));
193
      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
194 195 196
      expect(children, hasLength(3));
      expect(children.last['truncated'], isTrue);

197
      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
198 199 200 201 202
      expect(properties, hasLength(3));
      expect(properties.last['truncated'], isTrue);
    });

    test('delegateForAddingNodes', () {
203
      final Map<String, Object?> result = testTree.toDiagnosticsNode().toJsonMap(TestDiagnosticsSerializationDelegate(
204 205 206 207
          subtreeDepth: 5,
          includeProperties: true,
          nodeDelegator: (DiagnosticsNode node, DiagnosticsSerializationDelegate delegate) {
            return delegate.copyWith(includeProperties: false);
208
          },
209
      ));
210
      final List<Map<String, Object?>> properties = result['properties']! as List<Map<String, Object?>>;
211
      expect(properties, hasLength(7));
212
      expect(properties.every((Map<String, Object?> property) => !property.containsKey('properties')), isTrue);
213

214
      final List<Map<String, Object?>> children = result['children']! as List<Map<String, Object?>>;
215
      expect(children, hasLength(3));
216
      expect(children.every((Map<String, Object?> child) => !child.containsKey('properties')), isTrue);
217 218 219 220 221 222 223 224 225 226 227
    });
  });
}

class _TestElement extends Element {
  _TestElement() : super(const Placeholder());

  @override
  void performRebuild() {
    // Intentionally left empty.
  }
228 229 230

  @override
  bool get debugDoingBuild => throw UnimplementedError();
231 232 233 234
}

class TestTree extends Object with DiagnosticableTreeMixin {
  TestTree({
235
    this.name = '',
236 237 238 239 240 241 242 243
    this.style,
    this.children = const <TestTree>[],
    this.properties = const <DiagnosticsNode>[],
  });

  final String name;
  final List<TestTree> children;
  final List<DiagnosticsNode> properties;
244
  final DiagnosticsTreeStyle? style;
245 246

  @override
247
  List<DiagnosticsNode> debugDescribeChildren() => <DiagnosticsNode>[
248
    for (final TestTree child in children)
249
      child.toDiagnosticsNode(
250 251
        name: 'child ${child.name}',
        style: child.style,
252 253
      ),
  ];
254 255 256 257 258

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    if (style != null)
259
      properties.defaultDiagnosticsTreeStyle = style!;
260 261 262 263 264

    this.properties.forEach(properties.add);
  }
}

265 266 267 268
typedef NodeDelegator = DiagnosticsSerializationDelegate Function(DiagnosticsNode node, TestDiagnosticsSerializationDelegate delegate);
typedef NodeTruncator = List<DiagnosticsNode> Function(List<DiagnosticsNode> nodes, DiagnosticsNode? owner);
typedef NodeFilter = List<DiagnosticsNode> Function(List<DiagnosticsNode> nodes, DiagnosticsNode owner);

269 270 271 272 273 274 275 276 277 278 279 280
class TestDiagnosticsSerializationDelegate implements DiagnosticsSerializationDelegate {
  const TestDiagnosticsSerializationDelegate({
    this.includeProperties = false,
    this.subtreeDepth = 0,
    this.additionalNodePropertiesMap = const <String, Object>{},
    this.childFilter,
    this.propertyFilter,
    this.nodeTruncator,
    this.nodeDelegator,
  });

  final Map<String, Object> additionalNodePropertiesMap;
281 282 283 284
  final NodeFilter? childFilter;
  final NodeFilter? propertyFilter;
  final NodeTruncator? nodeTruncator;
  final NodeDelegator? nodeDelegator;
285 286 287 288 289 290 291 292 293

  @override
  Map<String, Object> additionalNodeProperties(DiagnosticsNode node) {
    return additionalNodePropertiesMap;
  }

  @override
  DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node) {
    if (nodeDelegator != null) {
294
      return nodeDelegator!(node, this);
295 296 297 298 299 300 301 302 303
    }
    return subtreeDepth > 0 ? copyWith(subtreeDepth: subtreeDepth - 1) : this;
  }

  @override
  bool get expandPropertyValues => false;

  @override
  List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
304
    return childFilter?.call(nodes, owner) ?? nodes;
305 306 307 308
  }

  @override
  List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
309
    return propertyFilter?.call(nodes, owner) ?? nodes;
310 311 312 313 314 315 316 317 318
  }

  @override
  final bool includeProperties;

  @override
  final int subtreeDepth;

  @override
319 320
  List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode? owner) {
    return nodeTruncator?.call(nodes, owner) ?? nodes;
321 322 323
  }

  @override
324
  DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties}) {
325 326 327 328 329 330 331 332 333 334 335
    return TestDiagnosticsSerializationDelegate(
      includeProperties: includeProperties ?? this.includeProperties,
      subtreeDepth: subtreeDepth ?? this.subtreeDepth,
      additionalNodePropertiesMap: additionalNodePropertiesMap,
      childFilter: childFilter,
      propertyFilter: propertyFilter,
      nodeTruncator: nodeTruncator,
      nodeDelegator: nodeDelegator,
    );
  }
}