var Scanner = HTMLTools.Scanner; var getContent = HTMLTools.Parse.getContent; var CharRef = HTML.CharRef; var Comment = HTML.Comment; var TemplateTag = HTMLTools.TemplateTag; var Attrs = HTML.Attrs; var BR = HTML.BR; var HR = HTML.HR; var INPUT = HTML.INPUT; var A = HTML.A; var DIV = HTML.DIV; var P = HTML.P; var TEXTAREA = HTML.TEXTAREA; Tinytest.add("html-tools - 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(BlazeTools.toJS(result), BlazeTools.toJS(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('abc', 'abc'); succeed('abc^^^', 'abc'); succeed('a<b', ['a', CharRef({html: '<', str: '<'}), 'b']); succeed('', Comment(' x ')); succeed('∾̳', CharRef({html: '∾̳', str: '\u223e\u0333'})); succeed('𝕫', CharRef({html: '𝕫', str: '\ud835\udd6b'})); succeed('&&>&g>;', ['&&>&g', CharRef({html: '>', str: '>'}), ';']); // Can't have an unescaped `&` if followed by certain names like `gt` fatal('>&'); // tests for other failure cases fatal('<'); succeed('
', BR()); succeed('
', BR()); fatal('
', 'self-close'); succeed('
', HR({id:'foo'})); succeed('
', HR({id:[CharRef({html:'<', str:'<'}), 'foo', CharRef({html:'>', str:'>'})]})); succeed('', INPUT({selected: ''})); succeed('', INPUT({selected: ''})); succeed('', INPUT({selected: ''})); var FOO = HTML.getTag('foo'); succeed('', FOO({bar: ''})); succeed('', FOO({bar: '', baz: ''})); succeed('', FOO({bar: 'x', baz: '', qux: 'y', blah: ''})); succeed('', FOO({bar: 'x', baz: '', qux: 'y', blah: ''})); fatal(''); fatal(''); fatal(''); succeed('
', BR({x: '&&&'})); succeed('


', [BR(), BR(), BR()]); succeed('aaa
\nbbb
\nccc
', ['aaa', BR(), '\nbbb', BR(), '\nccc', BR()]); succeed('', A()); fatal('<'); fatal(''); fatal('<'); fatal('Apple', 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('12345678', [A('1', B('2', C('3', D('4'), '5'), '6'), '7'), '8']); })(); fatal('hello there world'); // XXX support implied end tags in cases allowed by the spec fatal('

'); fatal('Foo'); fatal('Foo'); succeed('', TEXTAREA({value: "asdf"})); succeed('', TEXTAREA({x: "y", value: "asdf"})); succeed('', TEXTAREA({value: "

"})); succeed('', TEXTAREA({value: ["a", CharRef({html: '&', str: '&'}), "b"]})); succeed('', TEXTAREA({value: "\n', TEXTAREA()); succeed('', TEXTAREA({value: "asdf"})); succeed('', TEXTAREA({value: "\nasdf"})); succeed('', TEXTAREA({value: "\n"})); succeed('', TEXTAREA({value: "asdf\n"})); succeed('', TEXTAREA({value: ""})); succeed('', TEXTAREA({value: "asdf"})); fatal(''); succeed('', TEXTAREA({value: "&"})); succeed('asdf', [TEXTAREA({value: "x

', [DIV("x"), TEXTAREA()]); // CR/LF behavior succeed('', BR({x:''})); succeed('', BR({x:''})); succeed('
', BR({x:'y'})); succeed('
', BR({x:'y'})); succeed('
', BR({x:'y'})); succeed('
', BR({x:'y'})); succeed('
', BR({x:'y'})); succeed('', Comment('\n')); succeed('', Comment('\n')); succeed('', TEXTAREA({value: 'a\nb\nc'})); succeed('', TEXTAREA({value: 'a\nb\nc'})); succeed('
', BR({x:'\n\n'})); succeed('
', BR({x:'\n\n'})); succeed('
', BR({x:'y'})); fatal('
'); }); Tinytest.add("html-tools - parseFragment", function (test) { test.equal(BlazeTools.toJS(HTMLTools.parseFragment("

Hello

")), BlazeTools.toJS(DIV(P({id:'foo'}, 'Hello')))); _.each(['asdf
', '{{!foo}}
', '{{!foo}}
', 'asdf', '{{!foo}}', '{{!foo}} '], function (badFrag) { test.throws(function() { HTMLTools.parseFragment(badFrag); }, /Unexpected HTML close tag/); }); (function () { var p = HTMLTools.parseFragment('

'); test.equal(p.tagName, 'p'); test.equal(p.attrs, null); test.isTrue(p instanceof HTML.Tag); test.equal(p.children.length, 0); })(); (function () { var p = HTMLTools.parseFragment('

x

'); test.equal(p.tagName, 'p'); test.equal(p.attrs, null); test.isTrue(p instanceof HTML.Tag); test.equal(p.children.length, 1); test.equal(p.children[0], 'x'); })(); (function () { var p = HTMLTools.parseFragment('

xA

'); test.equal(p.tagName, 'p'); test.equal(p.attrs, null); test.isTrue(p instanceof HTML.Tag); test.equal(p.children.length, 2); test.equal(p.children[0], 'x'); test.isTrue(p.children[1] instanceof HTML.CharRef); test.equal(p.children[1].html, 'A'); test.equal(p.children[1].str, 'A'); })(); (function () { var pp = HTMLTools.parseFragment('

x

y

'); test.isTrue(pp instanceof Array); test.equal(pp.length, 2); test.equal(pp[0].tagName, 'p'); test.equal(pp[0].attrs, null); test.isTrue(pp[0] instanceof HTML.Tag); test.equal(pp[0].children.length, 1); test.equal(pp[0].children[0], 'x'); test.equal(pp[1].tagName, 'p'); test.equal(pp[1].attrs, null); test.isTrue(pp[1] instanceof HTML.Tag); test.equal(pp[1].children.length, 1); test.equal(pp[1].children[0], 'y'); })(); var scanner = new Scanner('asdf'); scanner.pos = 1; test.equal(HTMLTools.parseFragment(scanner), 'sdf'); test.throws(function () { var scanner = new Scanner('asdf

'); scanner.pos = 1; HTMLTools.parseFragment(scanner); }); }); Tinytest.add("html-tools - getTemplateTag", function (test) { // match a simple tag consisting of `{{`, an optional `!`, one // or more ASCII letters, spaces or html tags, and a closing `}}`. var mustache = /^\{\{(!?[a-zA-Z 0-9]+)\}\}/; // This implementation of `getTemplateTag` 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 getTemplateTag = 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 TemplateTag({ 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.getTemplateTag = getTemplateTag; var result; try { result = getContent(scanner); } catch (e) { result = String(e); } test.equal(scanner.pos, endPos); test.equal(BlazeTools.toJS(result), BlazeTools.toJS(expected)); }; var fatal = function (input, messageContains) { var scanner = new Scanner(input); scanner.getTemplateTag = getTemplateTag; 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}}', TemplateTag({stuff: 'foo'})); succeed('{{foo}}', A({href: "http://www.apple.com/"}, TemplateTag({stuff: 'foo'}))); // tags not parsed in comments succeed('', Comment("{{foo}}")); succeed('', Comment("{{foo")); succeed('&am{{foo}}p;', ['&am', TemplateTag({stuff: 'foo'}), 'p;']); // can't start a mustache and not finish it fatal('{{foo'); fatal('{{'); // no mustache allowed in tag name fatal('<{{a}}>'); fatal('<{{a}}b>'); fatal(''); // single curly brace is no biggie succeed('a{b', 'a{b'); succeed('
', BR({x:'{'})); succeed('
', BR({x:'{foo}'})); succeed('
', BR(Attrs(TemplateTag({stuff: 'x'})))); succeed('
', BR(Attrs(TemplateTag({stuff: 'x'}), TemplateTag({stuff: 'y'})))); succeed('
', BR(Attrs({y: ''}, TemplateTag({stuff: 'x'})))); fatal('
'); fatal('
'); succeed('
', BR({x: TemplateTag({stuff: 'y'}), z: ''})); succeed('
', BR({x: ['y', TemplateTag({stuff: 'z'}), 'w']})); succeed('
', BR({x: ['y', TemplateTag({stuff: 'z'}), 'w']})); succeed('
', BR({x: ['y ', TemplateTag({stuff: 'z'}), TemplateTag({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 `` or ``. succeed('
', BR({x: [TemplateTag({stuff: 'y'}), '/']})); succeed('
', BR({x: [TemplateTag({stuff: 'z'}), TemplateTag({stuff: 'w'})]})); fatal('
'); succeed('
', BR({x:CharRef({html: '&', str: '&'})})); // check tokenization of stache tags with spaces succeed('
', BR(Attrs(TemplateTag({stuff: 'x 1'})))); succeed('
', BR(Attrs(TemplateTag({stuff: 'x 1'}), TemplateTag({stuff: 'y 2'})))); succeed('
', BR(Attrs({y:''}, TemplateTag({stuff: 'x 1'})))); fatal('
'); fatal('
'); succeed('
', BR({x: TemplateTag({stuff: 'y 2'}), z: ''})); succeed('
', BR({x: ['y', TemplateTag({stuff: 'z 3'}), 'w']})); succeed('
', BR({x: ['y', TemplateTag({stuff: 'z 3'}), 'w']})); succeed('
', BR({x: ['y ', TemplateTag({stuff: 'z 3'}), TemplateTag({stuff: 'w 4'}), ' v']})); succeed('
', BR({x: [TemplateTag({stuff: 'y 2'}), '/']})); succeed('
', BR({x: [TemplateTag({stuff: 'z 3'}), TemplateTag({stuff: 'w 4'})]})); succeed('

', P()); succeed('x{{foo}}{{bar}}y', ['x', TemplateTag({stuff: 'foo'}), TemplateTag({stuff: 'bar'}), 'y']); succeed('x{{!foo}}{{!bar}}y', 'xy'); succeed('x{{!foo}}{{bar}}y', ['x', TemplateTag({stuff: 'bar'}), 'y']); succeed('x{{foo}}{{!bar}}y', ['x', TemplateTag({stuff: 'foo'}), 'y']); succeed('
{{!foo}}{{!bar}}
', DIV()); succeed('
{{!foo}}
{{!bar}}
', DIV(BR())); succeed('
{{!foo}} {{!bar}}
', DIV(" ")); succeed('
{{!foo}}
{{!bar}}
', DIV(" ", BR(), " ")); succeed('{{!
}}', null); succeed('{{!
}}', null); succeed('', null); succeed('{{!foo}}', null); succeed('', TEXTAREA(Attrs({x:"1"}, TemplateTag({stuff: 'a'}), TemplateTag({stuff: 'b'})))); });