| import 'dart:convert'; |
| import 'dart:io'; |
| import 'dart:mirrors'; |
| |
| import 'package:path/path.dart' as p; |
| |
| import 'package:html/dom.dart'; |
| import 'package:html/parser.dart' show parseFragment; |
| import 'package:markdown/markdown.dart' show markdownToHtml, ExtensionSet; |
| |
| // Locate the "tool" directory. Use mirrors so that this works with the test |
| // package, which loads this suite into an isolate. |
| String get toolDir => |
| p.dirname((reflect(loadCommonMarkSections) as ClosureMirror) |
| .function |
| .location |
| .sourceUri |
| .path); |
| |
| File getStatsFile(String prefix) => |
| new File(p.join(toolDir, '${prefix}_stats.json')); |
| |
| Map<String, List<CommonMarkTestCase>> loadCommonMarkSections( |
| String testPrefix) { |
| var testFile = new File(p.join(toolDir, '${testPrefix}_tests.json')); |
| var testsJson = testFile.readAsStringSync(); |
| |
| var testArray = jsonDecode(testsJson) as List<Map<String, dynamic>>; |
| |
| var sections = new Map<String, List<CommonMarkTestCase>>(); |
| |
| for (var exampleMap in testArray) { |
| var exampleTest = new CommonMarkTestCase.fromJson(exampleMap); |
| |
| var sectionList = |
| sections.putIfAbsent(exampleTest.section, () => <CommonMarkTestCase>[]); |
| |
| sectionList.add(exampleTest); |
| } |
| |
| return sections; |
| } |
| |
| class Config { |
| static final Config commonMarkConfig = |
| new Config._('common_mark', 'http://spec.commonmark.org/0.28/', null); |
| static final Config gfmConfig = new Config._( |
| 'gfm', 'https://github.github.com/gfm/', ExtensionSet.gitHubFlavored); |
| |
| final String prefix; |
| final String baseUrl; |
| final ExtensionSet extensionSet; |
| |
| Config._(this.prefix, this.baseUrl, this.extensionSet); |
| } |
| |
| class CommonMarkTestCase { |
| final String markdown; |
| final String section; |
| final int example; |
| final String html; |
| final int startLine; |
| final int endLine; |
| |
| CommonMarkTestCase(this.example, this.section, this.startLine, this.endLine, |
| this.markdown, this.html); |
| |
| factory CommonMarkTestCase.fromJson(Map<String, dynamic> json) { |
| return new CommonMarkTestCase( |
| json['example'] as int, |
| json['section'] as String, |
| json['start_line'] as int, |
| json['end_line'] as int, |
| json['markdown'] as String, |
| json['html'] as String); |
| } |
| } |
| |
| enum CompareLevel { strict, loose, fail, error } |
| |
| CompareLevel compareResult(Config config, CommonMarkTestCase expected, |
| {bool throwOnError: false, |
| bool verboseFail: false, |
| bool verboseLooseMatch: false}) { |
| String output; |
| try { |
| output = |
| markdownToHtml(expected.markdown, extensionSet: config.extensionSet); |
| } catch (err, stackTrace) { |
| if (throwOnError) { |
| rethrow; |
| } |
| if (verboseFail) { |
| _printVerboseFailure(config.baseUrl, 'ERROR', expected, expected.html, |
| 'Thrown: $err\n$stackTrace'); |
| } |
| |
| return CompareLevel.error; |
| } |
| |
| if (expected.html == output) { |
| return CompareLevel.strict; |
| } |
| |
| var expectedParsed = parseFragment(expected.html); |
| var actual = parseFragment(output); |
| |
| var looseMatch = _compareHtml(expectedParsed.children, actual.children); |
| |
| if (!looseMatch && verboseFail) { |
| _printVerboseFailure(config.baseUrl, 'FAIL', expected, |
| expectedParsed.outerHtml, actual.outerHtml); |
| } |
| |
| if (looseMatch && verboseLooseMatch) { |
| _printVerboseFailure( |
| config.baseUrl, 'LOOSE', expected, output, actual.outerHtml); |
| } |
| |
| return looseMatch ? CompareLevel.loose : CompareLevel.fail; |
| } |
| |
| String _indent(String s) => s.splitMapJoin('\n', onNonMatch: (n) => ' $n'); |
| |
| void _printVerboseFailure(String baseUrl, String message, |
| CommonMarkTestCase test, String expected, String actual) { |
| print('$message: $baseUrl#example-${test.example} ' |
| '@ ${test.section}'); |
| print('input:'); |
| print(_indent(test.markdown)); |
| print('expected:'); |
| print(_indent(expected)); |
| print('actual:'); |
| print(_indent(actual)); |
| print('-----------------------'); |
| } |
| |
| /// Compare two DOM trees for equality. |
| bool _compareHtml( |
| List<Element> expectedElements, List<Element> actualElements) { |
| if (expectedElements.length != actualElements.length) { |
| return false; |
| } |
| |
| for (var childNum = 0; childNum < expectedElements.length; childNum++) { |
| var expected = expectedElements[childNum]; |
| var actual = actualElements[childNum]; |
| |
| if (expected.runtimeType != actual.runtimeType) { |
| return false; |
| } |
| |
| if (expected.localName != actual.localName) { |
| return false; |
| } |
| |
| if (expected.attributes.length != actual.attributes.length) { |
| return false; |
| } |
| |
| var expectedAttrKeys = expected.attributes.keys.toList(); |
| expectedAttrKeys.sort(); |
| |
| var actualAttrKeys = actual.attributes.keys.toList(); |
| actualAttrKeys.sort(); |
| |
| for (var attrNum = 0; attrNum < actualAttrKeys.length; attrNum++) { |
| var expectedAttrKey = expectedAttrKeys[attrNum]; |
| var actualAttrKey = actualAttrKeys[attrNum]; |
| |
| if (expectedAttrKey != actualAttrKey) { |
| return false; |
| } |
| |
| if (expected.attributes[expectedAttrKey] != |
| actual.attributes[actualAttrKey]) { |
| return false; |
| } |
| } |
| |
| var childrenEqual = _compareHtml(expected.children, actual.children); |
| |
| if (!childrenEqual) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |