|
1 |
| -import 'package:checks/checks.dart'; |
| 1 | +import 'package:checks/context.dart'; |
| 2 | +import 'package:flutter/foundation.dart'; |
2 | 3 | import 'package:zulip/model/content.dart';
|
3 | 4 |
|
4 | 5 | extension ContentNodeChecks on Subject<ContentNode> {
|
5 | 6 | void equalsNode(ContentNode expected) {
|
6 |
| - // TODO: Make equalsNode output clearer on failure, applying Diagnosticable. |
7 |
| - // In particular (a) show the top-level expected node in one piece |
8 |
| - // (as well as the actual); (a') ideally, suppress on the "expected" side |
9 |
| - // the various predicates below, which should be redundant with just |
10 |
| - // the expected node; (b) show expected for the specific `equals` leaf. |
11 |
| - // See also comment on [ContentNode.toString]. |
12 |
| - if (expected is ZulipContent) { |
13 |
| - isA<ZulipContent>() |
14 |
| - .nodes.equalsNodes(expected.nodes); |
15 |
| - } else if (expected is UnimplementedBlockContentNode) { |
16 |
| - isA<UnimplementedBlockContentNode>() |
17 |
| - .debugHtmlText.equals(expected.debugHtmlText); |
18 |
| - } else if (expected is ParagraphNode) { |
19 |
| - isA<ParagraphNode>() |
20 |
| - ..wasImplicit.equals(expected.wasImplicit) |
21 |
| - ..nodes.equalsNodes(expected.nodes); |
22 |
| - } else if (expected is HeadingNode) { |
23 |
| - isA<HeadingNode>() |
24 |
| - ..level.equals(expected.level) |
25 |
| - ..nodes.equalsNodes(expected.nodes); |
26 |
| - } else if (expected is ListNode) { |
27 |
| - isA<ListNode>() |
28 |
| - ..style.equals(expected.style) |
29 |
| - ..items.deepEquals(expected.items.map( |
30 |
| - (item) => it()..isA<List<BlockContentNode>>().equalsNodes(item))); |
31 |
| - } else if (expected is QuotationNode) { |
32 |
| - isA<QuotationNode>() |
33 |
| - .nodes.equalsNodes(expected.nodes); |
34 |
| - } else if (expected is UnimplementedInlineContentNode) { |
35 |
| - isA<UnimplementedInlineContentNode>() |
36 |
| - .debugHtmlText.equals(expected.debugHtmlText); |
37 |
| - } else if (expected is StrongNode) { |
38 |
| - isA<StrongNode>() |
39 |
| - .nodes.equalsNodes(expected.nodes); |
40 |
| - } else if (expected is EmphasisNode) { |
41 |
| - isA<EmphasisNode>() |
42 |
| - .nodes.equalsNodes(expected.nodes); |
43 |
| - } else if (expected is InlineCodeNode) { |
44 |
| - isA<InlineCodeNode>() |
45 |
| - .nodes.equalsNodes(expected.nodes); |
46 |
| - } else if (expected is LinkNode) { |
47 |
| - isA<LinkNode>() |
48 |
| - .nodes.equalsNodes(expected.nodes); |
49 |
| - } else if (expected is UserMentionNode) { |
50 |
| - isA<UserMentionNode>() |
51 |
| - .nodes.equalsNodes(expected.nodes); |
52 |
| - } else { |
53 |
| - // The remaining node types have structural `==`. Use that. |
54 |
| - equals(expected); |
55 |
| - } |
| 7 | + return context.expect(() => prefixFirst('equals ', literal(expected)), (actual) { |
| 8 | + final which = _compareDiagnosticsNodes( |
| 9 | + actual.toDiagnosticsNode(), expected.toDiagnosticsNode()); |
| 10 | + return which == null ? null : Rejection(which: [ |
| 11 | + 'differs in that it:', |
| 12 | + ...indent(which), |
| 13 | + ]); |
| 14 | + }); |
56 | 15 | }
|
57 |
| - |
58 |
| - Subject<String> get debugHtmlText => has((n) => n.debugHtmlText, 'debugHtmlText'); |
59 | 16 | }
|
60 | 17 |
|
61 |
| -extension ZulipContentChecks on Subject<ZulipContent> { |
62 |
| - Subject<List<BlockContentNode>> get nodes => has((n) => n.nodes, 'nodes'); |
63 |
| -} |
| 18 | +Iterable<String>? _compareDiagnosticsNodes(DiagnosticsNode actual, DiagnosticsNode expected) { |
| 19 | + assert(actual is DiagnosticableTreeNode && expected is DiagnosticableTreeNode); |
64 | 20 |
|
65 |
| -extension BlockContentNodeListChecks on Subject<List<BlockContentNode>> { |
66 |
| - void equalsNodes(List<BlockContentNode> expected) { |
67 |
| - deepEquals(expected.map( |
68 |
| - (e) => it()..isA<BlockContentNode>().equalsNode(e))); |
69 |
| - // A shame we need the dynamic `isA` there. This |
70 |
| - // version hits a runtime type error: |
71 |
| - // .nodes.deepEquals(expected.nodes.map( |
72 |
| - // (e) => it<BlockContentNode>()..equalsNode(e))); |
73 |
| - // and with `it()` with no type argument, it doesn't type-check. |
74 |
| - // TODO(checks): report that as API feedback on deepEquals |
| 21 | + if (actual.value.runtimeType != expected.value.runtimeType) { |
| 22 | + return [ |
| 23 | + 'has type ${actual.value.runtimeType}', |
| 24 | + 'expected: ${expected.value.runtimeType}', |
| 25 | + ]; |
75 | 26 | }
|
76 |
| -} |
77 |
| - |
78 |
| -extension BlockInlineContainerNodeChecks on Subject<BlockInlineContainerNode> { |
79 |
| - Subject<List<InlineContentNode>> get nodes => has((n) => n.nodes, 'nodes'); |
80 |
| -} |
81 |
| - |
82 |
| -extension ParagraphNodeChecks on Subject<ParagraphNode> { |
83 |
| - Subject<bool> get wasImplicit => has((n) => n.wasImplicit, 'wasImplicit'); |
84 |
| -} |
85 |
| - |
86 |
| -extension HeadingNodeChecks on Subject<HeadingNode> { |
87 |
| - Subject<HeadingLevel> get level => has((n) => n.level, 'level'); |
88 |
| -} |
89 |
| - |
90 |
| -extension ListNodeChecks on Subject<ListNode> { |
91 |
| - Subject<ListStyle> get style => has((n) => n.style, 'style'); |
92 |
| - Subject<List<List<BlockContentNode>>> get items => has((n) => n.items, 'items'); |
93 |
| -} |
94 | 27 |
|
95 |
| -extension QuotationNodeChecks on Subject<QuotationNode> { |
96 |
| - Subject<List<BlockContentNode>> get nodes => has((n) => n.nodes, 'nodes'); |
97 |
| -} |
| 28 | + final actualProperties = actual.getProperties(); |
| 29 | + final expectedProperties = expected.getProperties(); |
| 30 | + assert(actualProperties.length == expectedProperties.length); |
| 31 | + for (int i = 0; i < actualProperties.length; i++) { |
| 32 | + assert(actualProperties[i].name == expectedProperties[i].name); |
| 33 | + if (actualProperties[i].value != expectedProperties[i].value) { |
| 34 | + return [ |
| 35 | + 'has ${actualProperties[i].name} that:', |
| 36 | + ...indent(prefixFirst('is ', literal(actualProperties[i].value))), |
| 37 | + ...indent(prefixFirst('expected: ', literal(expectedProperties[i].value))) |
| 38 | + ]; |
| 39 | + } |
| 40 | + } |
98 | 41 |
|
99 |
| -extension InlineContentNodeListChecks on Subject<List<InlineContentNode>> { |
100 |
| - void equalsNodes(List<InlineContentNode> expected) { |
101 |
| - deepEquals(expected.map( |
102 |
| - (e) => it()..isA<InlineContentNode>().equalsNode(e))); |
| 42 | + final actualChildren = actual.getChildren(); |
| 43 | + final expectedChildren = expected.getChildren(); |
| 44 | + if (actualChildren.length != expectedChildren.length) { |
| 45 | + return [ |
| 46 | + 'has ${actualChildren.length} children', |
| 47 | + 'expected: ${expectedChildren.length} children', |
| 48 | + ]; |
| 49 | + } |
| 50 | + for (int i = 0; i < actualChildren.length; i++) { |
| 51 | + final failure = _compareDiagnosticsNodes(actualChildren[i], expectedChildren[i]); |
| 52 | + if (failure != null) { |
| 53 | + final diagnosticable = actualChildren[i].value as Diagnosticable; |
| 54 | + return [ |
| 55 | + 'has child $i (${diagnosticable.toStringShort()}) that:', |
| 56 | + ...indent(failure), |
| 57 | + ]; |
| 58 | + } |
103 | 59 | }
|
104 |
| -} |
105 | 60 |
|
106 |
| -extension InlineContainerNodeChecks on Subject<InlineContainerNode> { |
107 |
| - Subject<List<InlineContentNode>> get nodes => has((n) => n.nodes, 'nodes'); |
| 61 | + return null; |
108 | 62 | }
|
0 commit comments