/// Additional feature tests that aren't based on test data.
library;
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';
import 'package:test/test.dart';
void main() {
_testElementSpans();
test('doctype is cloneable', () {
final doc = parse('');
final doctype = doc.nodes[0] as DocumentType;
expect(doctype.clone(false).toString(), '');
});
test('line counter', () {
// http://groups.google.com/group/html5lib-discuss/browse_frm/thread/f4f00e4a2f26d5c0
final doc = parse('
\nx\n>\n
');
expect(doc.body!.innerHtml, '
x\n>\n
');
});
test('namespace html elements on', () {
final doc = HtmlParser('', tree: TreeBuilder(true)).parse();
expect((doc.nodes[0] as Element).namespaceUri, Namespaces.html);
});
test('namespace html elements off', () {
final doc = HtmlParser('', tree: TreeBuilder(false)).parse();
expect((doc.nodes[0] as Element).namespaceUri, null);
});
test('parse error spans - full', () {
final parser = HtmlParser('''
''', generateSpans: true, sourceUrl: 'ParseError');
final doc = parser.parse();
expect(doc.body!.outerHtml, '\n \n \n\n');
expect(parser.errors.length, 1);
final 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, '');
expect(error.toString(), '''
On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
╷
4 │
│ ^^^^^^^^^^^^^^^
╵''');
});
test('parse error spans - minimal', () {
final parser = HtmlParser('''
''');
final doc = parser.parse();
expect(doc.body!.outerHtml, '\n \n \n\n');
expect(parser.errors.length, 1);
final error = parser.errors[0];
expect(error.errorCode, 'unexpected-doctype');
});
test('text spans should have the correct length', () {
final textContent = '\n hello {{name}}';
final html = '
$textContent
';
final doc = parse(html, generateSpans: true);
final text = doc.body!.nodes[0].nodes[0] as Text;
expect(text, const TypeMatcher());
expect(text.data, textContent);
expect(text.sourceSpan!.start.offset, html.indexOf(textContent));
expect(text.sourceSpan!.length, textContent.length);
});
test('attribute spans', () {
final text = '';
final doc = parse(text, generateSpans: true);
final 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);
final span = elem.attributeSpans!['extends']!;
expect(span.start.offset, text.indexOf('extends'));
expect(span.text, 'extends="x-bar"');
});
test('attribute value spans', () {
final text = '';
final doc = parse(text, generateSpans: true);
final elem = doc.querySelector('element')!;
expect(elem.attributeValueSpans!['quux'], null);
final span = elem.attributeValueSpans!['extends']!;
expect(span.start.offset, text.indexOf('x-bar'));
expect(span.text, 'x-bar');
});
test('attribute spans if no attributes', () {
final text = '';
final doc = parse(text, generateSpans: true);
final elem = doc.querySelector('element')!;
expect(elem.attributeSpans!['quux'], null);
expect(elem.attributeValueSpans!['quux'], null);
});
test('attribute spans if no attribute value', () {
final text = '';
final doc = parse(text, generateSpans: true);
final 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', () {
final text = '';
final doc = parse(text);
final elem = doc.querySelector('element')!;
expect(elem.sourceSpan, null);
expect(elem.attributeSpans!['quux'], null);
expect(elem.attributeSpans!['extends'], null);
});
test('attribute spans if value contains & (ambiguous ampersand)', () {
final expectedUrl = 'foo?key=value&key2=value2';
final text = '';
final doc = parse(text, generateSpans: true);
final elem = doc.querySelector('script')!;
final span = elem.attributeValueSpans!['src']!;
expect(span.start.offset, text.indexOf('foo'));
expect(span.text, expectedUrl);
});
test('void element innerHTML', () {
var doc = parse('');
expect(doc.body!.innerHtml, '');
doc = parse('');
expect(doc.body!.innerHtml, '');
doc = parse(' ');
expect(doc.body!.innerHtml, ' ');
doc = parse(' ');
expect(doc.body!.innerHtml, ' ');
});
test('empty document has html, body, and head', () {
final doc = parse('');
final html = '';
expect(doc.outerHtml, html);
expect(doc.documentElement!.outerHtml, html);
expect(doc.head!.outerHtml, '');
expect(doc.body!.outerHtml, '');
});
test('strange table case', () {
final doc = parse('
').body!;
expect(doc.innerHtml, '
');
});
group('html serialization', () {
test('attribute order', () {
// Note: the spec only requires a stable order.
// However, we preserve the input order via LinkedHashMap
final body = parse('').body!;
expect(body.innerHtml, '');
expect(body.querySelector('foo')!.attributes.remove('a'), '2');
expect(body.innerHtml, '');
body.querySelector('foo')!.attributes['a'] = '0';
expect(body.innerHtml, '');
});
test('escaping Text node in ').firstChild as Element;
expect(e.outerHtml, '');
});
test('escaping Text node in ', () {
final e = parseFragment('a && b').firstChild as Element;
expect(e.outerHtml, 'a && b');
});
test('Escaping attributes', () {
var e = parseFragment('
').firstChild as Element;
expect(e.outerHtml, '');
e = parseFragment('
').firstChild as Element;
expect(e.outerHtml, '');
});
test('Escaping non-breaking space', () {
final text = 'foO\u00A0bar';
expect(text.codeUnitAt(text.indexOf('O') + 1), 0xA0);
final e = parseFragment(text).firstChild as Element;
expect(e.outerHtml, 'foO bar');
});
test('Newline after
', () {
var e = parseFragment('
\n\nsome text').firstChild as Element;
expect((e.firstChild as Text).data, '\nsome text');
expect(e.outerHtml, '
\n\nsome text
');
e = parseFragment('
\nsome text').firstChild as Element;
expect((e.firstChild as Text).data, 'some text');
expect(e.outerHtml, '
some text
');
});
test('xml namespaces', () {
// Note: this is a nonsensical example, but it triggers the behavior
// we're looking for with attribute names in foreign content.
final doc = parse('''