@desertnet/html-parser
Version:
HTML parser and non-strict validator
373 lines (311 loc) • 11.1 kB
JavaScript
import {expect} from 'chai'
import {Token as ScannerToken} from '@desertnet/scanner'
import {RootNode, TagNode, TextNode, AttrNode, CloseTagNode, CommentNode, EntityNode} from '../lib/HTMLNode'
import HTMLParseError from '../lib/HTMLParseError'
describe("Foundation.HTML.Parser.HTMLNode", function () {
var node;
beforeEach(function () {
node = new RootNode();
node.addToken(new ScannerToken("text", "foo", 0, 1, 0));
node.addToken(new ScannerToken("text", "bar", 3, 1, 3));
})
describe("#tokens", function () {
it("should return the tokens in an array", function () {
expect(node.tokens).to.deep.equal([
new ScannerToken("text", "foo", 0, 1, 0),
new ScannerToken("text", "bar", 3, 1, 3)
]);
})
})
describe("#addToken", function () {
it("should add a new parser error when an error token type is added", function () {
var token = new ScannerToken("error", "foo", 45, 2, 33);
node.addToken(token);
var error = new HTMLParseError();
error.addToken(token);
expect(node.errors.shift().startIndex).to.be.equal(45);
})
})
describe("#indexRange", function () {
it("should return null if there are no tokens for this node", function () {
var incompleteNode = new RootNode();
expect(incompleteNode.indexRange).to.be.equal(null);
})
it("should return the range of character positions in the source string this node covers", function () {
expect(node.indexRange).to.deep.equal([0, 5]);
})
})
describe("#canHaveChildren", function () {
it("should throw an error on base class", function () {
expect(function () {
HTMLNode.prototype.canHaveChildren.call(node)
}).to.throw();
})
})
describe("#toString", function () {
it("should throw an error when called on base class", function () {
expect(function () {
HTMLNode.prototype.toString.call(node);
}).to.throw();
})
})
describe("#children", function () {
it("should return a copy of the node's children array", function () {
var tag = new TagNode();
tag.tagName = "foo";
var text = new TextNode();
tag.appendChild(text);
var childrenCopy = tag.children;
childrenCopy.pop();
expect(tag.children).to.deep.equal([text]);
})
it("should return null when the node cannot contain children", function () {
var text = new TextNode();
expect(text.children).to.be.equal(null);
})
it("should return null when node can contian children but there are none", function () {
var tag = new TagNode();
tag.tagName = "foo";
expect(tag.children).to.be.equal(null);
})
})
describe("#lastChild", function () {
it("should return the last child in the node's children array", function () {
var tag = new TagNode();
tag.tagName = "foo";
var text1 = new TextNode();
tag.appendChild(text1);
var text2 = new TextNode();
tag.appendChild(text2);
var text3 = new TextNode();
tag.appendChild(text3);
expect(tag.lastChild).to.be.equal(text3);
})
it("should return null when there are no child nodes", function () {
var tag = new TagNode();
tag.tagName = "foo";
expect(tag.lastChild).to.be.equal(null);
})
})
describe("#errors", function () {
it("should return not only this node's errors but its childrens' too", function () {
var error1 = new HTMLParseError();
var error2 = new HTMLParseError();
var tag = new TagNode();
tag.tagName = "div";
node.addError(error1);
tag.addError(error2);
node.appendChild(tag);
expect(node.errors).to.deep.equal([error1, error2]);
})
})
})
describe("RootNode", function () {
var root;
beforeEach(function () {
root = new RootNode();
})
describe("#canHaveChildren", function () {
it("should return true", function () {
expect(root.canHaveChildren).to.be.equal(true);
})
})
})
describe("TextNode", function () {
var text;
beforeEach(function () {
text = new TextNode();
text.addToken(new ScannerToken("text", "foo", 0, 1, 0));
})
describe("#canHaveChildren", function () {
it("should return true", function () {
expect(text.canHaveChildren).to.be.equal(false);
})
})
describe("#appendChild", function () {
it("should throw an error when called a because it doesn't support children", function () {
var node = new RootNode();
expect(function () {text.appendChild(node)}).to.throw();
})
})
describe("#toString", function () {
it("should return the string for text nodes", function () {
expect(text.toString()).to.be.equal("'foo'");
})
})
})
describe("TagNode", function () {
var p, br, closeP;
beforeEach(function () {
p = new TagNode();
p.addToken(new ScannerToken("tagStart", "<p", 0, 1, 0));
br = new TagNode();
br.addToken(new ScannerToken("tagStart", "<br", 0, 1, 0));
closeP = new CloseTagNode();
closeP.tagName = "p";
})
describe("#canHaveChildren", function () {
it("should return true for a tag that can have children", function () {
expect(p.canHaveChildren).to.be.equal(true);
})
it("should return false for a void tag", function () {
expect(br.canHaveChildren).to.be.equal(false);
})
})
describe("#addToken", function () {
it("should set the tagName when passed a tagStart token", function () {
expect(p.tagName).to.be.equal("p");
})
})
describe("#appendChild", function () {
it("should append a node to the list of child nodes", function () {
p.appendChild(br);
expect(p.children).to.deep.equal([br]);
})
it("should not append close tag nodes to the list of child nodes", function () {
p.appendChild(closeP);
expect(p.lastChild).to.be.equal(null);
})
it("should append bogus closing tags to the list of child nodes and not make it the closing tag", function () {
var closeDiv = new CloseTagNode();
closeDiv.tagName = "div";
p.appendChild(closeDiv);
expect(p.lastChild).to.be.equal(closeDiv);
expect(p.closingTag).to.be.equal(null);
})
})
describe("#closingTag", function () {
it("should return null when there is no closing tag", function () {
expect(p.closingTag).to.be.equal(null);
})
it("should return the close tag node when it has one", function () {
p.appendChild(closeP);
expect(p.closingTag).to.be.equal(closeP);
})
})
describe("#toString", function () {
it("should return a string with open and close tag for an empty p tag node", function () {
expect(p.toString()).to.be.equal("<p>");
})
it("should return a string with just open tag for void tags", function () {
expect(br.toString()).to.deep.equal("<br>");
})
it("should return a string with tag and its contents for a p tag with a text node", function () {
var text = new TextNode();
text.addToken(new ScannerToken("text", "foo", 0, 1, 0));
p.appendChild(text);
expect(p.toString()).to.be.equal("<p>'foo'");
})
})
describe("#tagName", function () {
it("should return the lowercased version of the tag name", function () {
var tag = new TagNode();
tag.tagName = "FOO";
expect(tag.tagName).to.be.equal("foo");
})
})
describe("#errors", function () {
it("should include errors in its attributes", function () {
var attr = new AttrNode();
var error = new HTMLParseError();
attr.addError(error);
p.addAttribute(attr);
expect(p.errors).to.deep.equal([error]);
})
it("should include errors in its closing tag", function () {
var error = new HTMLParseError();
closeP.addError(error);
p.appendChild(closeP);
expect(p.errors).to.deep.equal([error]);
})
})
})
describe("AttrNode", function () {
var dataAttr;
beforeEach(function () {
dataAttr = new AttrNode();
dataAttr.addToken(new ScannerToken("attributeStart", "data-foo", 0, 1, 0));
dataAttr.addToken(new ScannerToken("text", "bar", 0, 1, 0));
})
describe("#canHaveChildren", function () {
it("should return false for attribute nodes", function () {
expect(dataAttr.canHaveChildren).to.be.equal(false);
})
})
describe("#toString", function () {
it("should return a string with single quotes for the attribute value", function () {
expect(dataAttr.toString()).to.be.equal("data-foo='bar'");
})
})
})
describe("CommentNode", function () {
var comment;
beforeEach(function () {
comment = new CommentNode();
comment.addToken(new ScannerToken("text", "hello world", 0, 1, 0));
})
describe("#canHaveChildren", function () {
it("should return false for comment nodes", function () {
expect(comment.canHaveChildren).to.be.equal(false);
})
})
describe("#toString", function () {
it("should reutrn the comment in a string for comment nodes", function () {
expect(comment.toString()).to.be.equal("<!--hello world-->");
})
})
})
describe("EntityNode", function () {
var pooEnt;
beforeEach(function () {
pooEnt = new EntityNode();
pooEnt.addToken(new ScannerToken("hex", "#128169", 0, 1, 0));
})
describe("#canHaveChildren", function () {
it("should return false for entity nodes", function () {
expect(pooEnt.canHaveChildren).to.be.equal(false);
})
})
describe("#toString", function () {
it("should return the &entity; string for entity nodes", function () {
expect(pooEnt.toString()).to.be.equal("&(#128169);");
})
})
})
describe("CloseTagNode", function () {
var closeTag;
beforeEach(function () {
closeTag = new CloseTagNode();
closeTag.addToken(new ScannerToken("closeTagStart", "</p", 0, 1, 0));
closeTag.addToken(new ScannerToken("endTag", ">", 3, 1, 3));
})
describe("#canHaveChildren", function () {
it("should return false for close tag nodes", function () {
expect(closeTag.canHaveChildren).to.be.equal(false);
})
})
describe("#toString", function () {
it("should return the close tag string for close tag nodes", function () {
expect(closeTag.toString()).to.be.equal("</p>");
})
})
describe("#tagName", function () {
it("should return the lowercased version of the tag name", function () {
var tag = new CloseTagNode();
tag.tagName = "FOO";
expect(tag.tagName).to.be.equal("foo");
})
})
describe("#addToken", function () {
it("should set the tagName when passed a closeTagStart token", function () {
var closeTag = new CloseTagNode();
closeTag.addToken(new ScannerToken("closeTagStart", "</foo", 0, 1, 0));
expect(closeTag.tagName).to.be.equal("foo");
})
it("should set the tagName when passed a closeTag token", function () {
var closeTag = new CloseTagNode();
closeTag.addToken(new ScannerToken("closeTag", "</foobar>", 0, 1, 0));
expect(closeTag.tagName).to.be.equal("foobar");
})
})
})