| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file | |
| // for details. 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:markdown/markdown.dart'; | |
| import 'package:test/test.dart'; | |
| import 'util.dart'; | |
| void main() { | |
| group('NodeMatcher', () { | |
| // Simple element. | |
| final foo = Element('foo', []); | |
| // No nested elements. | |
| final fooTextAttr = Element('foo', [Text('text')], {'key': 'value'}); | |
| // Element which is self-closing. | |
| final fooEmpty = Element.empty('foo', {'key': 'value'}); | |
| // Nested elements. | |
| final nested = Element( | |
| 'outer', | |
| [ | |
| Text('<mid'), | |
| Element( | |
| 'middle', | |
| [ | |
| Text('<inner'), | |
| Element.empty('inner', {'innerKey': 'innerValue'}), | |
| Text('inner>'), | |
| ], | |
| {'midKey': 'midValue'}, | |
| ), | |
| Text('mid>'), | |
| ], | |
| {'outerKey': 'outerValue'}, | |
| ); | |
| group('TextMatcher', () { | |
| test('literal match', () { | |
| expect(Text('text'), isText('text')); | |
| expect(Text('text'), isText(Text('text'))); | |
| expect(Text('text'), TextMatcher('text')); | |
| expect(Text('text'), TextMatcher(Text('text'))); | |
| expect(Text('text'), TextMatcher.of(Text('text'))); | |
| }); | |
| test('string matcher match', () { | |
| expect(Text('text'), isText(startsWith('tex'))); | |
| expect(Text('text'), TextMatcher(startsWith('tex'))); | |
| }); | |
| test('fails when not same text', () { | |
| expect(Text('text'), isNot(isText('other'))); | |
| expect(Text('text'), isNot(Text('other'))); | |
| expect(Text('text'), isNot(TextMatcher('other'))); | |
| expect(Text('text'), isNot(TextMatcher(Text('other')))); | |
| }); | |
| test('fails when string matcher does not match', () { | |
| expect(Text('text'), isNot(isText(startsWith('not')))); | |
| expect(Text('text'), isNot(TextMatcher(startsWith('not')))); | |
| }); | |
| }); | |
| group('ElementMatcher', () { | |
| // Simple element. | |
| testMatcher('can pass element to `isElement`', foo, foo); | |
| testMatcher('can pass string tag', foo, 'foo'); | |
| testMatcher('can pass empty list of children', foo, 'foo', children: []); | |
| testMatcher( | |
| 'can pass empty map of attributes', | |
| foo, | |
| 'foo', | |
| attributes: {}, | |
| ); | |
| testMatcher( | |
| 'can pass null as children and empty map as attributes', | |
| foo, | |
| 'foo', | |
| children: [], | |
| attributes: {}, | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'empty children is not no children', | |
| foo, | |
| 'foo', | |
| children: [], | |
| attributes: {}, | |
| isEmpty: true, | |
| ); | |
| // Does not check children or attributes if `null`. | |
| testMatcher( | |
| 'does not check children or attributes if not provided', | |
| fooTextAttr, | |
| 'foo', | |
| ); | |
| testMatcher( | |
| 'can match Text children by strings', | |
| fooTextAttr, | |
| 'foo', | |
| children: ['text'], | |
| ); | |
| testMatcher( | |
| 'can match Text children by Text objects', | |
| fooTextAttr, | |
| 'foo', | |
| children: [Text('text')], | |
| ); | |
| testMatcher( | |
| 'can match Text children by TextMatchers', | |
| fooTextAttr, | |
| 'foo', | |
| children: [isText('text')], | |
| ); | |
| testMatcher( | |
| 'can match attributes without children', | |
| fooTextAttr, | |
| 'foo', | |
| attributes: {'key': 'value'}, | |
| ); | |
| testMatcher( | |
| 'can match attributes and children', | |
| fooTextAttr, | |
| 'foo', | |
| children: [isText('text')], | |
| attributes: {'key': 'value'}, | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'Non-null children must match', | |
| fooTextAttr, | |
| 'foo', | |
| children: [], | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'Text must match text content', | |
| fooTextAttr, | |
| 'foo', | |
| children: ['other text'], | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'No extra children', | |
| fooTextAttr, | |
| 'foo', | |
| children: ['text', 'more text'], | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'Non-null attributes must match every entry', | |
| fooTextAttr, | |
| 'foo', | |
| attributes: {}, | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'Non-null attributes must match every entry value', | |
| fooTextAttr, | |
| 'foo', | |
| attributes: {'key': 'not value'}, | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'Non-null attributes must not match other entires', | |
| fooTextAttr, | |
| 'foo', | |
| attributes: {'key': 'value', 'otherKey': 'otherValue'}, | |
| ); | |
| testMatcher( | |
| 'can use matchers for tag, children, and attribute values', | |
| fooTextAttr, | |
| startsWith('fo'), | |
| children: [isText(startsWith('tex'))], | |
| attributes: {'key': startsWith('val')}, | |
| ); | |
| testMatcher( | |
| 'can use matchers for entire children and attribute maps', | |
| fooTextAttr, | |
| startsWith('fo'), | |
| children: isNot(isEmpty), | |
| attributes: isNot(isEmpty), | |
| ); | |
| testMatcher( | |
| 'empty element', | |
| fooEmpty, | |
| 'foo', | |
| attributes: {'key': 'value'}, | |
| isEmpty: true, | |
| ); | |
| testMatcher( | |
| 'empty element has null children', | |
| fooEmpty, | |
| 'foo', | |
| children: isNull, | |
| attributes: {'key': 'value'}, | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'empty element has no children', | |
| fooEmpty, | |
| 'foo', | |
| children: [], | |
| attributes: {'key': 'value'}, | |
| ); | |
| testMatcher( | |
| fails: true, | |
| 'empty element is empty', | |
| fooEmpty, | |
| 'foo', | |
| attributes: {'key': 'value'}, | |
| isEmpty: false, | |
| ); | |
| // Matches self. | |
| testMatcher('nested elements', nested, nested); | |
| // Matches the same structure created as a matcher. | |
| testMatcher( | |
| 'nested elements', | |
| nested, | |
| 'outer', | |
| children: [ | |
| isText('<mid'), | |
| isElement( | |
| 'middle', | |
| [ | |
| isText('<inner'), | |
| isEmptyElement('inner', {'innerKey': 'innerValue'}), | |
| isText('inner>'), | |
| ], | |
| {'midKey': 'midValue'}, | |
| ), | |
| isText('mid>'), | |
| ], | |
| attributes: {'outerKey': 'outerValue'}, | |
| ); | |
| }); | |
| test('NodeMatcher.of', () { | |
| expect(foo, NodeMatcher.of(foo)); | |
| expect(fooTextAttr, NodeMatcher.of(fooTextAttr)); | |
| expect(nested, NodeMatcher.of(nested)); | |
| expect(Text('text'), NodeMatcher.of(Text('text'))); | |
| }); | |
| }); | |
| } | |
| /// Helper function used by [ElementMatcher] testing. | |
| /// | |
| /// Tests that the same parmeters to `isElement` and `ElementMatcher` works. | |
| void testMatcher( | |
| String name, | |
| Object item, | |
| Object tag, { | |
| Object? children, | |
| Object? attributes, | |
| bool? isEmpty, | |
| bool fails = false, | |
| }) { | |
| group(name, () { | |
| test('using isElement', () { | |
| final Matcher matcher = isElement(tag, children, attributes, isEmpty); | |
| expect(item, fails ? isNot(matcher) : matcher); | |
| }); | |
| test('using ElementMatcher', () { | |
| final Matcher matcher = ElementMatcher( | |
| tag, | |
| children: children, | |
| attributes: attributes, | |
| isEmpty: isEmpty, | |
| ); | |
| expect(item, fails ? isNot(matcher) : matcher); | |
| }); | |
| }); | |
| } |