'],
[html.Expansion, 'count', 'plural', 1, '{count, plural, =0 {msg}}'],
[html.ExpansionCase, '=0', 2, '=0 {msg}'],
]);
});
it('should not report a value span for an attribute without a value', () => {
const ast = parser.parse('
', 'TestComp');
expect((ast.rootNodes[0] as html.Element).attrs[0].valueSpan).toBeUndefined();
});
it('should report a value span for an attribute with a value', () => {
const ast = parser.parse('
', 'TestComp');
const attr = (ast.rootNodes[0] as html.Element).attrs[0];
expect(attr.valueSpan !.start.offset).toEqual(9);
expect(attr.valueSpan !.end.offset).toEqual(13);
});
});
describe('visitor', () => {
it('should visit text nodes', () => {
const result = humanizeDom(parser.parse('text', 'TestComp'));
expect(result).toEqual([[html.Text, 'text', 0]]);
});
it('should visit element nodes', () => {
const result = humanizeDom(parser.parse('
', 'TestComp'));
expect(result).toEqual([[html.Element, 'div', 0]]);
});
it('should visit attribute nodes', () => {
const result = humanizeDom(parser.parse('
', 'TestComp'));
expect(result).toContain([html.Attribute, 'id', 'foo']);
});
it('should visit all nodes', () => {
const result =
parser.parse('
a b
', 'TestComp');
const accumulator: html.Node[] = [];
const visitor = new class {
visit(node: html.Node, context: any) { accumulator.push(node); }
visitElement(element: html.Element, context: any): any {
html.visitAll(this, element.attrs);
html.visitAll(this, element.children);
}
visitAttribute(attribute: html.Attribute, context: any): any {}
visitText(text: html.Text, context: any): any {}
visitComment(comment: html.Comment, context: any): any {}
visitExpansion(expansion: html.Expansion, context: any): any {
html.visitAll(this, expansion.cases);
}
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any {}
};
html.visitAll(visitor, result.rootNodes);
expect(accumulator.map(n => n.constructor)).toEqual([
html.Element, html.Attribute, html.Element, html.Attribute, html.Text, html.Element,
html.Text
]);
});
it('should skip typed visit if visit() returns a truthy value', () => {
const visitor = new class {
visit(node: html.Node, context: any) { return true; }
visitElement(element: html.Element, context: any): any { throw Error('Unexpected'); }
visitAttribute(attribute: html.Attribute, context: any): any {
throw Error('Unexpected');
}
visitText(text: html.Text, context: any): any { throw Error('Unexpected'); }
visitComment(comment: html.Comment, context: any): any { throw Error('Unexpected'); }
visitExpansion(expansion: html.Expansion, context: any): any {
throw Error('Unexpected');
}
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any {
throw Error('Unexpected');
}
};
const result = parser.parse('
', 'TestComp');
const traversal = html.visitAll(visitor, result.rootNodes);
expect(traversal).toEqual([true, true]);
});
});
describe('errors', () => {
it('should report unexpected closing tags', () => {
const errors = parser.parse('
', 'TestComp').errors;
expect(errors.length).toEqual(1);
expect(humanizeErrors(errors)).toEqual([[
'p',
'Unexpected closing tag "p". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags',
'0:5'
]]);
});
it('should report subsequent open tags without proper close tag', () => {
const errors = parser.parse('
', 'TestComp').errors;
expect(errors.length).toEqual(1);
expect(humanizeErrors(errors)).toEqual([[
'div',
'Unexpected closing tag "div". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags',
'0:4'
]]);
});
it('should report closing tag for void elements', () => {
const errors = parser.parse('
', 'TestComp').errors;
expect(errors.length).toEqual(1);
expect(humanizeErrors(errors)).toEqual([
['input', 'Void elements do not have end tags "input"', '0:7']
]);
});
it('should report self closing html element', () => {
const errors = parser.parse('
', 'TestComp').errors;
expect(errors.length).toEqual(1);
expect(humanizeErrors(errors)).toEqual([
['p', 'Only void and foreign elements can be self closed "p"', '0:0']
]);
});
it('should report self closing custom element', () => {
const errors = parser.parse('
', 'TestComp').errors;
expect(errors.length).toEqual(1);
expect(humanizeErrors(errors)).toEqual([
['my-cmp', 'Only void and foreign elements can be self closed "my-cmp"', '0:0']
]);
});
it('should also report lexer errors', () => {
const errors = parser.parse('
', 'TestComp').errors;
expect(errors.length).toEqual(2);
expect(humanizeErrors(errors)).toEqual([
[TokenType.COMMENT_START, 'Unexpected character "e"', '0:3'],
[
'p',
'Unexpected closing tag "p". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags',
'0:14'
]
]);
});
});
});
});
}
export function humanizeErrors(errors: ParseError[]): any[] {
return errors.map(e => {
if (e instanceof TreeError) {
// Parser errors
return [
e.elementName, e.msg, humanizeLineColumn(e.span.start)];
}
// Tokenizer errors
return [(e).tokenType, e.msg, humanizeLineColumn(e.span.start)];
});
}