| Index: mojo/public/dart/third_party/html/test/parser_feature_test.dart
|
| diff --git a/mojo/public/dart/third_party/html/test/parser_feature_test.dart b/mojo/public/dart/third_party/html/test/parser_feature_test.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..54d68a945845ac4d4d03f87f991fc5c713ad3d79
|
| --- /dev/null
|
| +++ b/mojo/public/dart/third_party/html/test/parser_feature_test.dart
|
| @@ -0,0 +1,476 @@
|
| +/// Additional feature tests that aren't based on test data.
|
| +library parser_feature_test;
|
| +
|
| +import 'package:unittest/unittest.dart';
|
| +import 'package:html/dom.dart';
|
| +import 'package:html/parser.dart';
|
| +import 'package:html/src/constants.dart';
|
| +import 'package:html/src/encoding_parser.dart';
|
| +import 'package:html/src/treebuilder.dart';
|
| +import 'package:source_span/source_span.dart';
|
| +
|
| +main() {
|
| + _testElementSpans();
|
| + test('doctype is cloneable', () {
|
| + var doc = parse('<!doctype HTML>');
|
| + DocumentType doctype = doc.nodes[0];
|
| + expect(doctype.clone(false).toString(), '<!DOCTYPE html>');
|
| + });
|
| +
|
| + test('line counter', () {
|
| + // http://groups.google.com/group/html5lib-discuss/browse_frm/thread/f4f00e4a2f26d5c0
|
| + var doc = parse("<pre>\nx\n>\n</pre>");
|
| + expect(doc.body.innerHtml, "<pre>x\n>\n</pre>");
|
| + });
|
| +
|
| + test('namespace html elements on', () {
|
| + var doc = new HtmlParser('', tree: new TreeBuilder(true)).parse();
|
| + expect(doc.nodes[0].namespaceUri, Namespaces.html);
|
| + });
|
| +
|
| + test('namespace html elements off', () {
|
| + var doc = new HtmlParser('', tree: new TreeBuilder(false)).parse();
|
| + expect(doc.nodes[0].namespaceUri, null);
|
| + });
|
| +
|
| + test('parse error spans - full', () {
|
| + var parser = new HtmlParser('''
|
| +<!DOCTYPE html>
|
| +<html>
|
| + <body>
|
| + <!DOCTYPE html>
|
| + </body>
|
| +</html>
|
| +''', generateSpans: true, sourceUrl: 'ParseError');
|
| + var doc = parser.parse();
|
| + expect(doc.body.outerHtml, '<body>\n \n \n\n</body>');
|
| + expect(parser.errors.length, 1);
|
| + ParseError error = parser.errors[0];
|
| + expect(error.errorCode, 'unexpected-doctype');
|
| +
|
| + // Note: these values are 0-based, but the printed format is 1-based.
|
| + expect(error.span.start.line, 3);
|
| + expect(error.span.end.line, 3);
|
| + expect(error.span.start.column, 2);
|
| + expect(error.span.end.column, 17);
|
| + expect(error.span.text, '<!DOCTYPE html>');
|
| +
|
| + expect(error.toString(), '''
|
| +On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
|
| + <!DOCTYPE html>
|
| + ^^^^^^^^^^^^^^^''');
|
| + });
|
| +
|
| + test('parse error spans - minimal', () {
|
| + var parser = new HtmlParser('''
|
| +<!DOCTYPE html>
|
| +<html>
|
| + <body>
|
| + <!DOCTYPE html>
|
| + </body>
|
| +</html>
|
| +''');
|
| + var doc = parser.parse();
|
| + expect(doc.body.outerHtml, '<body>\n \n \n\n</body>');
|
| + expect(parser.errors.length, 1);
|
| + ParseError error = parser.errors[0];
|
| + expect(error.errorCode, 'unexpected-doctype');
|
| + expect(error.span.start.line, 3);
|
| + // Note: error position is at the end, not the beginning
|
| + expect(error.span.start.column, 17);
|
| + });
|
| +
|
| + test('text spans should have the correct length', () {
|
| + var textContent = '\n hello {{name}}';
|
| + var html = '<body><div>$textContent</div>';
|
| + var doc = parse(html, generateSpans: true);
|
| + Text text = doc.body.nodes[0].nodes[0];
|
| + expect(text, new isInstanceOf<Text>());
|
| + expect(text.data, textContent);
|
| + expect(text.sourceSpan.start.offset, html.indexOf(textContent));
|
| + expect(text.sourceSpan.length, textContent.length);
|
| + });
|
| +
|
| + test('attribute spans', () {
|
| + var text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('element');
|
| + expect(elem.sourceSpan.start.offset, 0);
|
| + expect(elem.sourceSpan.end.offset, text.length);
|
| + expect(elem.sourceSpan.text, text);
|
| +
|
| + expect(elem.attributeSpans['quux'], null);
|
| +
|
| + var span = elem.attributeSpans['extends'];
|
| + expect(span.start.offset, text.indexOf('extends'));
|
| + expect(span.text, 'extends="x-bar"');
|
| + });
|
| +
|
| + test('attribute value spans', () {
|
| + var text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('element');
|
| +
|
| + expect(elem.attributeValueSpans['quux'], null);
|
| +
|
| + var span = elem.attributeValueSpans['extends'];
|
| + expect(span.start.offset, text.indexOf('x-bar'));
|
| + expect(span.text, 'x-bar');
|
| + });
|
| +
|
| + test('attribute spans if no attributes', () {
|
| + var text = '<element>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('element');
|
| +
|
| + expect(elem.attributeSpans['quux'], null);
|
| + expect(elem.attributeValueSpans['quux'], null);
|
| + });
|
| +
|
| + test('attribute spans if no attribute value', () {
|
| + var text = '<foo template>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('foo');
|
| +
|
| + expect(
|
| + elem.attributeSpans['template'].start.offset, text.indexOf('template'));
|
| + expect(elem.attributeValueSpans.containsKey('template'), false);
|
| + });
|
| +
|
| + test('attribute spans null if code parsed without spans', () {
|
| + var text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
|
| + var doc = parse(text);
|
| + var elem = doc.querySelector('element');
|
| + expect(elem.sourceSpan, null);
|
| + expect(elem.attributeSpans['quux'], null);
|
| + expect(elem.attributeSpans['extends'], null);
|
| + });
|
| +
|
| + test('void element innerHTML', () {
|
| + var doc = parse('<div></div>');
|
| + expect(doc.body.innerHtml, '<div></div>');
|
| + doc = parse('<body><script></script></body>');
|
| + expect(doc.body.innerHtml, '<script></script>');
|
| + doc = parse('<br>');
|
| + expect(doc.body.innerHtml, '<br>');
|
| + doc = parse('<br><foo><bar>');
|
| + expect(doc.body.innerHtml, '<br><foo><bar></bar></foo>');
|
| + });
|
| +
|
| + test('empty document has html, body, and head', () {
|
| + var doc = parse('');
|
| + var html = '<html><head></head><body></body></html>';
|
| + expect(doc.outerHtml, html);
|
| + expect(doc.documentElement.outerHtml, html);
|
| + expect(doc.head.outerHtml, '<head></head>');
|
| + expect(doc.body.outerHtml, '<body></body>');
|
| + });
|
| +
|
| + test('strange table case', () {
|
| + var doc = parse('<table><tbody><foo>').body;
|
| + expect(doc.innerHtml, '<foo></foo><table><tbody></tbody></table>');
|
| + });
|
| +
|
| + group('html serialization', () {
|
| + test('attribute order', () {
|
| + // Note: the spec only requires a stable order.
|
| + // However, we preserve the input order via LinkedHashMap
|
| + var body = parse('<foo d=1 a=2 c=3 b=4>').body;
|
| + expect(body.innerHtml, '<foo d="1" a="2" c="3" b="4"></foo>');
|
| + expect(body.querySelector('foo').attributes.remove('a'), '2');
|
| + expect(body.innerHtml, '<foo d="1" c="3" b="4"></foo>');
|
| + body.querySelector('foo').attributes['a'] = '0';
|
| + expect(body.innerHtml, '<foo d="1" c="3" b="4" a="0"></foo>');
|
| + });
|
| +
|
| + test('escaping Text node in <script>', () {
|
| + Element e = parseFragment('<script>a && b</script>').firstChild;
|
| + expect(e.outerHtml, '<script>a && b</script>');
|
| + });
|
| +
|
| + test('escaping Text node in <span>', () {
|
| + Element e = parseFragment('<span>a && b</span>').firstChild;
|
| + expect(e.outerHtml, '<span>a && b</span>');
|
| + });
|
| +
|
| + test('Escaping attributes', () {
|
| + Element e = parseFragment('<div class="a<b>">').firstChild;
|
| + expect(e.outerHtml, '<div class="a<b>"></div>');
|
| + e = parseFragment('<div class=\'a"b\'>').firstChild;
|
| + expect(e.outerHtml, '<div class="a"b"></div>');
|
| + });
|
| +
|
| + test('Escaping non-breaking space', () {
|
| + var text = '<span>foO\u00A0bar</span>';
|
| + expect(text.codeUnitAt(text.indexOf('O') + 1), 0xA0);
|
| + Element e = parseFragment(text).firstChild;
|
| + expect(e.outerHtml, '<span>foO bar</span>');
|
| + });
|
| +
|
| + test('Newline after <pre>', () {
|
| + Element e = parseFragment('<pre>\n\nsome text</span>').firstChild;
|
| + expect((e.firstChild as Text).data, '\nsome text');
|
| + expect(e.outerHtml, '<pre>\n\nsome text</pre>');
|
| +
|
| + e = parseFragment('<pre>\nsome text</span>').firstChild;
|
| + expect((e.firstChild as Text).data, 'some text');
|
| + expect(e.outerHtml, '<pre>some text</pre>');
|
| + });
|
| +
|
| + test('xml namespaces', () {
|
| + // Note: this is a nonsensical example, but it triggers the behavior
|
| + // we're looking for with attribute names in foreign content.
|
| + var doc = parse('''
|
| + <body>
|
| + <svg>
|
| + <desc xlink:type="simple"
|
| + xlink:href="http://example.com/logo.png"
|
| + xlink:show="new"></desc>
|
| + ''');
|
| + var n = doc.querySelector('desc');
|
| + var keys = n.attributes.keys.toList();
|
| + expect(keys[0], new isInstanceOf<AttributeName>());
|
| + expect(keys[0].prefix, 'xlink');
|
| + expect(keys[0].namespace, 'http://www.w3.org/1999/xlink');
|
| + expect(keys[0].name, 'type');
|
| +
|
| + expect(n.outerHtml, '<desc xlink:type="simple" '
|
| + 'xlink:href="http://example.com/logo.png" xlink:show="new"></desc>');
|
| + });
|
| + });
|
| +
|
| + test('error printing without spans', () {
|
| + var parser = new HtmlParser('foo');
|
| + var doc = parser.parse();
|
| + expect(doc.body.innerHtml, 'foo');
|
| + expect(parser.errors.length, 1);
|
| + expect(parser.errors[0].errorCode, 'expected-doctype-but-got-chars');
|
| + expect(parser.errors[0].message,
|
| + 'Unexpected non-space characters. Expected DOCTYPE.');
|
| + expect(parser.errors[0].toString(),
|
| + 'ParserError on line 1, column 4: Unexpected non-space characters. '
|
| + 'Expected DOCTYPE.\n'
|
| + 'foo\n'
|
| + ' ^');
|
| + });
|
| +
|
| + test('Element.text', () {
|
| + var doc = parseFragment('<div>foo<div>bar</div>baz</div>');
|
| + var e = doc.firstChild;
|
| + var text = e.firstChild;
|
| + expect((text as Text).data, 'foo');
|
| + expect(e.text, 'foobarbaz');
|
| +
|
| + e.text = 'FOO';
|
| + expect(e.nodes.length, 1);
|
| + expect(e.firstChild, isNot(text), reason: 'should create a new tree');
|
| + expect((e.firstChild as Text).data, 'FOO');
|
| + expect(e.text, 'FOO');
|
| + });
|
| +
|
| + test('Text.text', () {
|
| + var doc = parseFragment('<div>foo<div>bar</div>baz</div>');
|
| + var e = doc.firstChild;
|
| + Text text = e.firstChild;
|
| + expect(text.data, 'foo');
|
| + expect(text.text, 'foo');
|
| +
|
| + text.text = 'FOO';
|
| + expect(text.data, 'FOO');
|
| + expect(e.text, 'FOObarbaz');
|
| + expect(text.text, 'FOO');
|
| + });
|
| +
|
| + test('Comment.text', () {
|
| + var doc = parseFragment('<div><!--foo-->bar</div>');
|
| + var e = doc.firstChild;
|
| + var c = e.firstChild;
|
| + expect((c as Comment).data, 'foo');
|
| + expect(c.text, 'foo');
|
| + expect(e.text, 'bar');
|
| +
|
| + c.text = 'qux';
|
| + expect(c.data, 'qux');
|
| + expect(c.text, 'qux');
|
| + expect(e.text, 'bar');
|
| + });
|
| +
|
| + test('foreignObject end tag', () {
|
| + var p = new HtmlParser('''
|
| +<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"
|
| + version="1.1">
|
| + <foreignObject width="320px" height="200px">
|
| + <x-flow></x-flow>
|
| + </foreignObject>
|
| +</svg>''');
|
| + var doc = p.parseFragment();
|
| + expect(p.errors, isEmpty);
|
| + var svg = doc.querySelector('svg');
|
| + expect(svg.children[0].children[0].localName, 'x-flow');
|
| + });
|
| +
|
| + group('Encoding pre-parser', () {
|
| + getEncoding(s) => new EncodingParser(s.codeUnits).getEncoding();
|
| +
|
| + test('gets encoding from meta charset', () {
|
| + expect(getEncoding('<meta charset="utf-16">'), 'utf-16');
|
| + });
|
| +
|
| + test('gets encoding from meta in head', () {
|
| + expect(getEncoding('<head><meta charset="utf-16">'), 'utf-16');
|
| + });
|
| +
|
| + test('skips comments', () {
|
| + expect(getEncoding('<!--comment--><meta charset="utf-16">'), 'utf-16');
|
| + });
|
| +
|
| + test('stops if no match', () {
|
| + // missing closing tag
|
| + expect(getEncoding('<meta charset="utf-16"'), null);
|
| + });
|
| +
|
| + test('ignores whitespace', () {
|
| + expect(getEncoding(' <meta charset="utf-16">'), 'utf-16');
|
| + });
|
| + });
|
| +}
|
| +
|
| +_testElementSpans() {
|
| + assertSpan(SourceSpan span, int offset, int end, String text) {
|
| + expect(span, isNotNull);
|
| + expect(span.start.offset, offset);
|
| + expect(span.end.offset, end);
|
| + expect(span.text, text);
|
| + }
|
| +
|
| + group('element spans', () {
|
| + test('html and body', () {
|
| + var text = '<html><body>123</body></html>';
|
| + var doc = parse(text, generateSpans: true);
|
| + {
|
| + var elem = doc.querySelector('html');
|
| + assertSpan(elem.sourceSpan, 0, 6, '<html>');
|
| + assertSpan(elem.endSourceSpan, 22, 29, '</html>');
|
| + }
|
| + {
|
| + var elem = doc.querySelector('body');
|
| + assertSpan(elem.sourceSpan, 6, 12, '<body>');
|
| + assertSpan(elem.endSourceSpan, 15, 22, '</body>');
|
| + }
|
| + });
|
| +
|
| + test('normal', () {
|
| + var text = '<div><element><span></span></element></div>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('element');
|
| + assertSpan(elem.sourceSpan, 5, 14, '<element>');
|
| + assertSpan(elem.endSourceSpan, 27, 37, '</element>');
|
| + });
|
| +
|
| + test('block', () {
|
| + var text = '<div>123</div>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('div');
|
| + assertSpan(elem.sourceSpan, 0, 5, '<div>');
|
| + assertSpan(elem.endSourceSpan, 8, 14, '</div>');
|
| + });
|
| +
|
| + test('form', () {
|
| + var text = '<form>123</form>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('form');
|
| + assertSpan(elem.sourceSpan, 0, 6, '<form>');
|
| + assertSpan(elem.endSourceSpan, 9, 16, '</form>');
|
| + });
|
| +
|
| + test('p explicit end', () {
|
| + var text = '<p>123</p>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('p');
|
| + assertSpan(elem.sourceSpan, 0, 3, '<p>');
|
| + assertSpan(elem.endSourceSpan, 6, 10, '</p>');
|
| + });
|
| +
|
| + test('p implicit end', () {
|
| + var text = '<div><p>123<p>456</div>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('p');
|
| + assertSpan(elem.sourceSpan, 5, 8, '<p>');
|
| + expect(elem.endSourceSpan, isNull);
|
| + });
|
| +
|
| + test('li', () {
|
| + var text = '<li>123</li>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('li');
|
| + assertSpan(elem.sourceSpan, 0, 4, '<li>');
|
| + assertSpan(elem.endSourceSpan, 7, 12, '</li>');
|
| + });
|
| +
|
| + test('heading', () {
|
| + var text = '<h1>123</h1>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('h1');
|
| + assertSpan(elem.sourceSpan, 0, 4, '<h1>');
|
| + assertSpan(elem.endSourceSpan, 7, 12, '</h1>');
|
| + });
|
| +
|
| + test('formatting', () {
|
| + var text = '<b>123</b>';
|
| + var doc = parse(text, generateSpans: true);
|
| + var elem = doc.querySelector('b');
|
| + assertSpan(elem.sourceSpan, 0, 3, '<b>');
|
| + assertSpan(elem.endSourceSpan, 6, 10, '</b>');
|
| + });
|
| +
|
| + test('table tbody', () {
|
| + var text = '<table><tbody> </tbody></table>';
|
| + var doc = parse(text, generateSpans: true);
|
| + {
|
| + var elem = doc.querySelector('tbody');
|
| + assertSpan(elem.sourceSpan, 7, 14, '<tbody>');
|
| + assertSpan(elem.endSourceSpan, 16, 24, '</tbody>');
|
| + }
|
| + });
|
| +
|
| + test('table tr td', () {
|
| + var text = '<table><tr><td>123</td></tr></table>';
|
| + var doc = parse(text, generateSpans: true);
|
| + {
|
| + var elem = doc.querySelector('table');
|
| + assertSpan(elem.sourceSpan, 0, 7, '<table>');
|
| + assertSpan(elem.endSourceSpan, 28, 36, '</table>');
|
| + }
|
| + {
|
| + var elem = doc.querySelector('tr');
|
| + assertSpan(elem.sourceSpan, 7, 11, '<tr>');
|
| + assertSpan(elem.endSourceSpan, 23, 28, '</tr>');
|
| + }
|
| + {
|
| + var elem = doc.querySelector('td');
|
| + assertSpan(elem.sourceSpan, 11, 15, '<td>');
|
| + assertSpan(elem.endSourceSpan, 18, 23, '</td>');
|
| + }
|
| + });
|
| +
|
| + test('select optgroup option', () {
|
| + var text = '<select><optgroup><option>123</option></optgroup></select>';
|
| + var doc = parse(text, generateSpans: true);
|
| + {
|
| + var elem = doc.querySelector('select');
|
| + assertSpan(elem.sourceSpan, 0, 8, '<select>');
|
| + assertSpan(elem.endSourceSpan, 49, 58, '</select>');
|
| + }
|
| + {
|
| + var elem = doc.querySelector('optgroup');
|
| + assertSpan(elem.sourceSpan, 8, 18, '<optgroup>');
|
| + assertSpan(elem.endSourceSpan, 38, 49, '</optgroup>');
|
| + }
|
| + {
|
| + var elem = doc.querySelector('option');
|
| + assertSpan(elem.sourceSpan, 18, 26, '<option>');
|
| + assertSpan(elem.endSourceSpan, 29, 38, '</option>');
|
| + }
|
| + });
|
| + });
|
| +}
|
|
|