Files
meteor/packages/html/parse_tests.js
2013-11-12 11:35:09 -08:00

295 lines
9.3 KiB
JavaScript

var Scanner = HTML._$.Scanner;
var getContent = HTML._$.getContent;
var CharRef = HTML.CharRef;
var Comment = HTML.Comment;
var Special = HTML.Special;
var BR = HTML.Tag.BR;
var HR = HTML.Tag.HR;
var INPUT = HTML.Tag.INPUT;
var A = HTML.Tag.A
var DIV = HTML.Tag.DIV;
var P = HTML.Tag.P;
Tinytest.add("html - parser getContent", function (test) {
var succeed = function (input, expected) {
var endPos = input.indexOf('^^^');
if (endPos < 0)
endPos = input.length;
var scanner = new Scanner(input.replace('^^^', ''));
var result = getContent(scanner);
test.equal(scanner.pos, endPos);
test.equal(UI.toCode(result), UI.toCode(expected));
};
var fatal = function (input, messageContains) {
var scanner = new Scanner(input);
var error;
try {
getContent(scanner);
} catch (e) {
error = e;
}
test.isTrue(error);
if (messageContains)
test.isTrue(messageContains && error.message.indexOf(messageContains) >= 0, error.message);
};
succeed('', null);
succeed('^^^</', null);
succeed('abc', 'abc');
succeed('abc^^^</x>', 'abc');
succeed('a&lt;b', ['a', CharRef({html: '&lt;', str: '<'}), 'b']);
succeed('<!-- x -->', Comment(' x '));
succeed('&acE;', CharRef({html: '&acE;', str: '\u223e\u0333'}));
succeed('&zopf;', CharRef({html: '&zopf;', str: '\ud835\udd6b'}));
succeed('&&>&g&gt;;', ['&&>&g', CharRef({html: '&gt;', str: '>'}), ';']);
// Can't have an unescaped `&` if followed by certain names like `gt`
fatal('&gt&');
// tests for other failure cases
fatal('<');
succeed('<br>', BR());
succeed('<br/>', BR());
fatal('<div/>', 'self-close');
succeed('<hr id=foo>', HR({id:'foo'}));
succeed('<hr id=&lt;foo&gt;>', HR({id:[CharRef({html:'&lt;', str:'<'}),
'foo',
CharRef({html:'&gt;', str:'>'})]}));
succeed('<input selected>', INPUT({selected: ''}));
succeed('<br x=&&&>', BR({x: '&&&'}));
succeed('<br><br><br>', [BR(), BR(), BR()]);
succeed('aaa<br>\nbbb<br>\nccc<br>', ['aaa', BR(), '\nbbb', BR(), '\nccc', BR()]);
succeed('<a></a>', A());
fatal('<');
fatal('<a');
fatal('<a>');
fatal('<a><');
fatal('<a></');
fatal('<a></a');
succeed('<a href="http://www.apple.com/">Apple</a>',
A({href: "http://www.apple.com/"}, 'Apple'));
(function () {
var A = HTML.getTag('A');
var B = HTML.getTag('B');
var C = HTML.getTag('C');
var D = HTML.getTag('D');
succeed('<a>1<b>2<c>3<d>4</d>5</c>6</b>7</a>8',
[A('1', B('2', C('3', D('4'), '5'), '6'), '7'), '8']);
})();
fatal('<b>hello <i>there</b> world</i>');
// XXX support implied end tags in cases allowed by the spec
fatal('<p>');
fatal('<a>Foo</a/>');
fatal('<a>Foo</a b=c>');
});
Tinytest.add("html - parseFragment", function (test) {
test.equal(UI.toCode(HTML.parseFragment("<div><p id=foo>Hello</p></div>")),
UI.toCode(DIV(P({id:'foo'}, 'Hello'))));
test.throws(function() {
HTML.parseFragment('asdf</a>');
});
(function () {
var p = HTML.parseFragment('<p></p>');
test.equal(p.tagName, 'P');
test.equal(p.attrs, null);
test.equal(HTML.typeOf(p), 'tag');
test.equal(p.length, 0);
})();
(function () {
var p = HTML.parseFragment('<p>x</p>');
test.equal(p.tagName, 'P');
test.equal(p.attrs, null);
test.equal(HTML.typeOf(p), 'tag');
test.equal(p.length, 1);
test.equal(p[0], 'x');
})();
(function () {
var p = HTML.parseFragment('<p>x&#65;</p>');
test.equal(p.tagName, 'P');
test.equal(p.attrs, null);
test.equal(HTML.typeOf(p), 'tag');
test.equal(p.length, 2);
test.equal(p[0], 'x');
test.equal(p[1].tagName, 'CharRef');
test.equal(p[1].length, 0);
test.equal(p[1].attrs, { html: '&#65;', str: 'A' });
test.equal(HTML.typeOf(p[1]), 'charref');
})();
(function () {
var pp = HTML.parseFragment('<p>x</p><p>y</p>');
test.equal(HTML.typeOf(pp), 'array');
test.equal(pp.length, 2);
test.equal(pp[0].tagName, 'P');
test.equal(pp[0].attrs, null);
test.equal(HTML.typeOf(pp[0]), 'tag');
test.equal(pp[0].length, 1);
test.equal(pp[0][0], 'x');
test.equal(pp[1].tagName, 'P');
test.equal(pp[1].attrs, null);
test.equal(HTML.typeOf(pp[1]), 'tag');
test.equal(pp[1].length, 1);
test.equal(pp[1][0], 'y');
})();
});
Tinytest.add("html - getSpecialTag", function (test) {
// match a simple tag consisting of `{{`, an optional `!`, one
// or more ASCII letters or spaces, and a closing `}}`.
var mustache = /^\{\{(!?[a-zA-Z 0-9]+)\}\}/;
// This implementation of `getSpecialTag` looks for "{{" and if it
// finds it, it will match the regex above or fail fatally trying.
// The object it returns is opaque to the tokenizer/parser and can
// be anything we want.
var getSpecialTag = function (scanner, position) {
if (! (scanner.peek() === '{' && // one-char peek is just an optimization
scanner.rest().slice(0, 2) === '{{'))
return null;
var match = mustache.exec(scanner.rest());
if (! match)
scanner.fatal("Bad mustache");
scanner.pos += match[0].length;
if (match[1].charAt(0) === '!')
return null; // `{{!foo}}` is like a comment
return { stuff: match[1] };
};
var succeed = function (input, expected) {
var endPos = input.indexOf('^^^');
if (endPos < 0)
endPos = input.length;
var scanner = new Scanner(input.replace('^^^', ''));
scanner.getSpecialTag = getSpecialTag;
var result;
try {
result = getContent(scanner);
} catch (e) {
result = String(e);
}
test.equal(scanner.pos, endPos);
test.equal(UI.toCode(result), UI.toCode(expected));
};
var fatal = function (input, messageContains) {
var scanner = new Scanner(input);
scanner.getSpecialTag = getSpecialTag;
var error;
try {
getContent(scanner);
} catch (e) {
error = e;
}
test.isTrue(error);
if (messageContains)
test.isTrue(messageContains && error.message.indexOf(messageContains) >= 0, error.message);
};
succeed('{{foo}}', Special({stuff: 'foo'}));
succeed('<a href=http://www.apple.com/>{{foo}}</a>',
A({href: "http://www.apple.com/"}, Special({stuff: 'foo'})));
// tags not parsed in comments
succeed('<!--{{foo}}-->', Comment("{{foo}}"));
succeed('<!--{{foo-->', Comment("{{foo"));
succeed('&am{{foo}}p;', ['&am', Special({stuff: 'foo'}), 'p;']);
// can't start a mustache and not finish it
fatal('{{foo');
fatal('<a>{{</a>');
// no mustache allowed in tag name
fatal('<{{a}}>');
fatal('<{{a}}b>');
fatal('<a{{b}}>');
// single curly brace is no biggie
succeed('a{b', 'a{b');
succeed('<br x={ />', BR({x:'{'}));
succeed('<br x={foo} />', BR({x:'{foo}'}));
succeed('<br {{x}}>', BR({$specials: [Special({stuff: 'x'})]}));
succeed('<br {{x}} {{y}}>', BR({$specials: [Special({stuff: 'x'}),
Special({stuff: 'y'})]}));
succeed('<br {{x}} y>', BR({$specials: [Special({stuff: 'x'})], y:''}));
fatal('<br {{x}}y>');
fatal('<br {{x}}=y>');
succeed('<br x={{y}} z>', BR({x: Special({stuff: 'y'}), z: ''}));
succeed('<br x=y{{z}}w>', BR({x: ['y', Special({stuff: 'z'}), 'w']}));
succeed('<br x="y{{z}}w">', BR({x: ['y', Special({stuff: 'z'}), 'w']}));
succeed('<br x="y {{z}}{{w}} v">', BR({x: ['y ', Special({stuff: 'z'}),
Special({stuff: 'w'}), ' v']}));
// Slash is parsed as part of unquoted attribute! This is consistent with
// the HTML tokenization spec. It seems odd for some inputs but is probably
// for cases like `<a href=http://foo.com/>` or `<a href=/foo/>`.
succeed('<br x={{y}}/>', BR({x: [Special({stuff: 'y'}), '/']}));
succeed('<br x={{z}}{{w}}>', BR({x: [Special({stuff: 'z'}),
Special({stuff: 'w'})]}));
fatal('<br x="y"{{z}}>');
succeed('<br x=&amp;>', BR({x:CharRef({html: '&amp;', str: '&'})}));
// check tokenization of stache tags with spaces
succeed('<br {{x 1}}>', BR({$specials: [Special({stuff: 'x 1'})]}));
succeed('<br {{x 1}} {{y 2}}>', BR({$specials: [Special({stuff: 'x 1'}),
Special({stuff: 'y 2'})]}));
succeed('<br {{x 1}} y>', BR({$specials: [Special({stuff: 'x 1'})], y:''}));
fatal('<br {{x 1}}y>');
fatal('<br {{x 1}}=y>');
succeed('<br x={{y 2}} z>', BR({x: Special({stuff: 'y 2'}), z: ''}));
succeed('<br x=y{{z 3}}w>', BR({x: ['y', Special({stuff: 'z 3'}), 'w']}));
succeed('<br x="y{{z 3}}w">', BR({x: ['y', Special({stuff: 'z 3'}), 'w']}));
succeed('<br x="y {{z 3}}{{w 4}} v">', BR({x: ['y ', Special({stuff: 'z 3'}),
Special({stuff: 'w 4'}), ' v']}));
succeed('<br x={{y 2}}/>', BR({x: [Special({stuff: 'y 2'}), '/']}));
succeed('<br x={{z 3}}{{w 4}}>', BR({x: [Special({stuff: 'z 3'}),
Special({stuff: 'w 4'})]}));
succeed('<p></p>', P());
succeed('x{{foo}}{{bar}}y', ['x', Special({stuff: 'foo'}),
Special({stuff: 'bar'}), 'y']);
succeed('x{{!foo}}{{!bar}}y', 'xy');
succeed('x{{!foo}}{{bar}}y', ['x', Special({stuff: 'bar'}), 'y']);
succeed('x{{foo}}{{!bar}}y', ['x', Special({stuff: 'foo'}), 'y']);
succeed('', null);
succeed('{{!foo}}', null);
});