/// Support code for the tests in this directory. library; import 'dart:collection'; import 'dart:io'; import 'dart:isolate'; import 'package:html/dom.dart'; import 'package:html/dom_parsing.dart'; import 'package:html/src/treebuilder.dart'; import 'package:path/path.dart' as p; typedef TreeBuilderFactory = TreeBuilder Function(bool namespaceHTMLElements); Map? _treeTypes; Map? get treeTypes { // TODO(jmesserly): add DOM here once it's implemented _treeTypes ??= {'simpletree': TreeBuilder.new}; return _treeTypes; } Future get testDirectory async { final packageUriDir = p.dirname(p.fromUri(await Isolate.resolvePackageUri( Uri(scheme: 'package', path: 'html/html.dart')))); // Assume pub layout - root is parent directory to package URI (`lib/`). final rootPackageDir = p.dirname(packageUriDir); return p.join(rootPackageDir, 'test'); } Stream dataFiles(String subdirectory) async* { final dir = Directory(p.join(await testDirectory, 'data', subdirectory)); await for (final file in dir.list()) { if (file is! File) continue; yield file.path; } } // TODO(jmesserly): make this class simpler. We could probably split on // "\n#" instead of newline and remove a lot of code. class TestData extends IterableBase> { final String _text; final String newTestHeading; TestData(String filename, [this.newTestHeading = 'data']) // Note: can't use readAsLinesSync here because it splits on \r : _text = File(filename).readAsStringSync(); // Note: in Python this was a generator, but since we can't do that in Dart, // it's easier to convert it into an upfront computation. @override Iterator> get iterator => _getData().iterator; List> _getData() { var data = {}; String? key; final List> result = >[]; final lines = _text.split('\n'); // Remove trailing newline to match Python if (lines.last == '') { lines.removeLast(); } for (var line in lines) { final heading = sectionHeading(line); if (heading != null) { if (data.isNotEmpty && heading == newTestHeading) { // Remove trailing newline data[key!] = data[key]!.substring(0, data[key]!.length - 1); result.add(normaliseOutput(data)); data = {}; } key = heading; data[key] = ''; } else if (key != null) { data[key] = '${data[key]}$line\n'; } } if (data.isNotEmpty) { result.add(normaliseOutput(data)); } return result; } /// If the current heading is a test section heading return the heading, /// otherwise return null. static String? sectionHeading(String line) { return line.startsWith('#') ? line.substring(1).trim() : null; } static Map normaliseOutput(Map data) { // Remove trailing newlines data.forEach((key, value) { if (value.endsWith('\n')) { data[key] = value.substring(0, value.length - 1); } }); return data; } } /// Serialize the [document] into the html5 test data format. String testSerializer(Node document) { return (TestSerializer()..visit(document)).toString(); } /// Serializes the DOM into test format. See [testSerializer]. class TestSerializer extends TreeVisitor { final StringBuffer _str; int _indent = 0; String _spaces = ''; TestSerializer() : _str = StringBuffer(); @override String toString() => _str.toString(); int get indent => _indent; set indent(int value) { if (_indent == value) return; _spaces = ' ' * value; _indent = value; } void _newline() { if (_str.length > 0) _str.write('\n'); _str.write('|$_spaces'); } @override void visitNodeFallback(Node node) { _newline(); _str.write(node); visitChildren(node); } @override void visitChildren(Node node) { indent += 2; for (var child in node.nodes) { visit(child); } indent -= 2; } @override void visitDocument(Document node) => _visitDocumentOrFragment(node); void _visitDocumentOrFragment(Node node) { indent += 1; for (var child in node.nodes) { visit(child); } indent -= 1; } @override void visitDocumentFragment(DocumentFragment node) => _visitDocumentOrFragment(node); @override void visitElement(Element node) { _newline(); _str.write(node); if (node.attributes.isNotEmpty) { indent += 2; final keys = node.attributes.keys.toList(); keys.sort((x, y) { if (x is String) return x.compareTo(y as String); if (x is AttributeName) return x.compareTo(y as AttributeName); throw StateError('Cannot sort'); }); for (var key in keys) { final v = node.attributes[key]; if (key is AttributeName) { final attr = key; key = '${attr.prefix} ${attr.name}'; } _newline(); _str.write('$key="$v"'); } indent -= 2; } visitChildren(node); } }