2016-06-23 12:47:54 -04:00
/ * *
* @license
* Copyright Google Inc . All Rights Reserved .
*
* Use of this source code is governed by an MIT - style license that can be
* found in the LICENSE file at https : //angular.io/license
* /
2016-08-01 15:19:09 -04:00
import * as html from '../../src/ml_parser/ast' ;
import { HtmlParser , ParseTreeResult , TreeError } from '../../src/ml_parser/html_parser' ;
import { TokenType } from '../../src/ml_parser/lexer' ;
2016-07-21 14:41:25 -04:00
import { ParseError } from '../../src/parse_util' ;
2016-06-09 17:53:03 -04:00
2016-07-21 16:56:58 -04:00
import { humanizeDom , humanizeDomSourceSpans , humanizeLineColumn } from './ast_spec_utils' ;
2015-08-25 18:36:02 -04:00
2017-12-16 17:42:55 -05:00
{
2015-10-07 12:34:21 -04:00
describe ( 'HtmlParser' , ( ) = > {
2016-11-12 08:08:58 -05:00
let parser : HtmlParser ;
2016-06-22 20:25:42 -04:00
2020-04-08 13:14:18 -04:00
beforeEach ( ( ) = > {
parser = new HtmlParser ( ) ;
} ) ;
2015-08-25 18:36:02 -04:00
2015-10-07 12:34:21 -04:00
describe ( 'parse' , ( ) = > {
2015-09-11 16:35:46 -04:00
describe ( 'text nodes' , ( ) = > {
it ( 'should parse root level text nodes' , ( ) = > {
2016-07-21 16:56:58 -04:00
expect ( humanizeDom ( parser . parse ( 'a' , 'TestComp' ) ) ) . toEqual ( [ [ html . Text , 'a' , 0 ] ] ) ;
2015-09-11 16:35:46 -04:00
} ) ;
it ( 'should parse text nodes inside regular elements' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<div>a</div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'div' , 0 ] , [ html . Text , 'a' , 1 ]
2016-06-08 19:38:52 -04:00
] ) ;
2015-09-11 16:35:46 -04:00
} ) ;
2015-08-25 18:36:02 -04:00
2017-01-09 16:16:46 -05:00
it ( 'should parse text nodes inside <ng-template> elements' , ( ) = > {
expect ( humanizeDom ( parser . parse ( '<ng-template>a</ng-template>' , 'TestComp' ) ) ) . toEqual ( [
[ html . Element , 'ng-template' , 0 ] , [ html . Text , 'a' , 1 ]
] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
it ( 'should parse CDATA' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<![CDATA[text]]>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Text , 'text' , 0 ]
2016-06-08 19:38:52 -04:00
] ) ;
2015-09-11 16:35:46 -04:00
} ) ;
2015-08-25 18:36:02 -04:00
} ) ;
2015-09-11 16:35:46 -04:00
describe ( 'elements' , ( ) = > {
it ( 'should parse root level elements' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<div></div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'div' , 0 ]
2016-06-08 19:38:52 -04:00
] ) ;
2015-09-11 16:35:46 -04:00
} ) ;
it ( 'should parse elements inside of regular elements' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<div><span></span></div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'div' , 0 ] , [ html . Element , 'span' , 1 ]
2016-06-08 19:38:52 -04:00
] ) ;
2015-09-11 16:35:46 -04:00
} ) ;
2017-01-09 16:16:46 -05:00
it ( 'should parse elements inside <ng-template> elements' , ( ) = > {
expect ( humanizeDom ( parser . parse ( '<ng-template><span></span></ng-template>' , 'TestComp' ) ) )
. toEqual ( [ [ html . Element , 'ng-template' , 0 ] , [ html . Element , 'span' , 1 ] ] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
it ( 'should support void elements' , ( ) = > {
expect ( humanizeDom ( parser . parse ( '<link rel="author license" href="/about">' , 'TestComp' ) ) )
. toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'link' , 0 ] ,
[ html . Attribute , 'rel' , 'author license' ] ,
[ html . Attribute , 'href' , '/about' ] ,
2015-11-10 18:56:25 -05:00
] ) ;
} ) ;
2015-12-07 12:06:03 -05:00
it ( 'should not error on void elements from HTML5 spec' ,
( ) = > { // http://www.w3.org/TR/html-markup/syntax.html#syntax-elements without:
// <base> - it can be present in head only
// <meta> - it can be present in head only
// <command> - obsolete
// <keygen> - obsolete
2020-04-08 13:14:18 -04:00
[ '<map><area></map>' ,
'<div><br></div>' ,
'<colgroup><col></colgroup>' ,
'<div><embed></div>' ,
'<div><hr></div>' ,
'<div><img></div>' ,
'<div><input></div>' ,
'<object><param>/<object>' ,
'<audio><source></audio>' ,
'<audio><track></audio>' ,
2015-12-07 12:06:03 -05:00
'<p><wbr></p>' ,
2020-04-08 13:14:18 -04:00
] . forEach ( ( html ) = > {
expect ( parser . parse ( html , 'TestComp' ) . errors ) . toEqual ( [ ] ) ;
} ) ;
2015-12-07 12:06:03 -05:00
} ) ;
2015-12-01 16:01:05 -05:00
it ( 'should close void elements on text nodes' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<p>before<br>after</p>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'p' , 0 ] ,
[ html . Text , 'before' , 1 ] ,
[ html . Element , 'br' , 1 ] ,
[ html . Text , 'after' , 1 ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-12-01 16:01:05 -05:00
} ) ;
2015-11-10 18:56:25 -05:00
it ( 'should support optional end tags' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<div><p>1<p>2</div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'div' , 0 ] ,
[ html . Element , 'p' , 1 ] ,
[ html . Text , '1' , 2 ] ,
[ html . Element , 'p' , 1 ] ,
[ html . Text , '2' , 2 ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
it ( 'should support nested elements' , ( ) = > {
expect ( humanizeDom ( parser . parse ( '<ul><li><ul><li></li></ul></li></ul>' , 'TestComp' ) ) )
. toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'ul' , 0 ] ,
[ html . Element , 'li' , 1 ] ,
[ html . Element , 'ul' , 2 ] ,
[ html . Element , 'li' , 3 ] ,
2015-11-10 18:56:25 -05:00
] ) ;
} ) ;
2019-03-11 09:26:20 -04:00
/ * *
* Certain elements ( like < tr > or < col > ) require parent elements of a certain type ( ex . < tr >
* can only be inside < tbody > / < thead > ) . The Angular HTML parser doesn ' t validate those
* HTML compliancy rules as "problematic" elements can be projected - in such case HTML ( as
* written in an Angular template ) might be "invalid" ( spec - wise ) but the resulting DOM will
* still be correct .
* /
it ( 'should not wraps elements in a required parent' , ( ) = > {
expect ( humanizeDom ( parser . parse ( '<div><tr></tr></div>' , 'TestComp' ) ) ) . toEqual ( [
[ html . Element , 'div' , 0 ] ,
[ html . Element , 'tr' , 1 ] ,
2016-06-15 12:45:19 -04:00
] ) ;
} ) ;
2017-05-15 04:27:42 -04:00
it ( 'should support explicit namespace' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<myns:div></myns:div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , ':myns:div' , 0 ]
2016-06-08 19:38:52 -04:00
] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
2017-05-15 04:27:42 -04:00
it ( 'should support implicit namespace' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<svg></svg>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , ':svg:svg' , 0 ]
2016-06-08 19:38:52 -04:00
] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
it ( 'should propagate the namespace' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<myns:div><p></p></myns:div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , ':myns:div' , 0 ] ,
[ html . Element , ':myns:p' , 1 ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
2015-11-23 19:02:19 -05:00
it ( 'should match closing tags case sensitive' , ( ) = > {
2016-11-12 08:08:58 -05:00
const errors = parser . parse ( '<DiV><P></p></dIv>' , 'TestComp' ) . errors ;
2015-11-23 19:02:19 -05:00
expect ( errors . length ) . toEqual ( 2 ) ;
2016-06-08 19:38:52 -04:00
expect ( humanizeErrors ( errors ) ) . toEqual ( [
2017-03-10 16:05:17 -05:00
[
'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:8'
] ,
[
'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:12'
] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-09-11 16:35:46 -04:00
} ) ;
2015-12-03 19:10:20 -05:00
it ( 'should support self closing void elements' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<input />' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'input' , 0 ]
2016-06-08 19:38:52 -04:00
] ) ;
2015-12-03 19:10:20 -05:00
} ) ;
it ( 'should support self closing foreign elements' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<math />' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , ':math:math' , 0 ]
2016-06-08 19:38:52 -04:00
] ) ;
2015-12-03 19:10:20 -05:00
} ) ;
2015-12-05 03:15:18 -05:00
it ( 'should ignore LF immediately after textarea, pre and listing' , ( ) = > {
expect ( humanizeDom ( parser . parse (
'<p>\n</p><textarea>\n</textarea><pre>\n\n</pre><listing>\n\n</listing>' ,
'TestComp' ) ) )
. toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'p' , 0 ] ,
[ html . Text , '\n' , 1 ] ,
[ html . Element , 'textarea' , 0 ] ,
[ html . Element , 'pre' , 0 ] ,
[ html . Text , '\n' , 1 ] ,
[ html . Element , 'listing' , 0 ] ,
[ html . Text , '\n' , 1 ] ,
2015-12-05 03:15:18 -05:00
] ) ;
} ) ;
2015-08-25 18:36:02 -04:00
} ) ;
2015-09-11 16:35:46 -04:00
describe ( 'attributes' , ( ) = > {
2015-11-10 18:56:25 -05:00
it ( 'should parse attributes on regular elements case sensitive' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<div kEy="v" key2=v2></div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'div' , 0 ] ,
[ html . Attribute , 'kEy' , 'v' ] ,
[ html . Attribute , 'key2' , 'v2' ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-09-11 16:35:46 -04:00
} ) ;
2015-10-07 12:34:21 -04:00
it ( 'should parse attributes without values' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<div k></div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'div' , 0 ] ,
[ html . Attribute , 'k' , '' ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-10-07 12:34:21 -04:00
} ) ;
2015-11-06 14:31:03 -05:00
it ( 'should parse attributes on svg elements case sensitive' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<svg viewBox="0"></svg>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , ':svg:svg' , 0 ] ,
[ html . Attribute , 'viewBox' , '0' ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-10-14 12:04:38 -04:00
} ) ;
2017-01-09 16:16:46 -05:00
it ( 'should parse attributes on <ng-template> elements' , ( ) = > {
expect ( humanizeDom ( parser . parse ( '<ng-template k="v"></ng-template>' , 'TestComp' ) ) )
. toEqual ( [
[ html . Element , 'ng-template' , 0 ] ,
[ html . Attribute , 'k' , 'v' ] ,
] ) ;
2015-09-11 16:35:46 -04:00
} ) ;
2015-08-25 18:36:02 -04:00
2016-04-12 14:46:39 -04:00
it ( 'should support namespace' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<svg:use xlink:href="Port" />' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , ':svg:use' , 0 ] ,
[ html . Attribute , ':xlink:href' , 'Port' ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
} ) ;
describe ( 'comments' , ( ) = > {
2016-03-06 23:21:20 -05:00
it ( 'should preserve comments' , ( ) = > {
2016-06-08 19:38:52 -04:00
expect ( humanizeDom ( parser . parse ( '<!-- comment --><div></div>' , 'TestComp' ) ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Comment , 'comment' , 0 ] ,
[ html . Element , 'div' , 0 ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
} ) ;
2016-06-08 19:38:52 -04:00
describe ( 'expansion forms' , ( ) = > {
it ( 'should parse out expansion forms' , ( ) = > {
2016-11-12 08:08:58 -05:00
const parsed = parser . parse (
2016-06-08 19:38:52 -04:00
` <div>before{messages.length, plural, =0 {You have <b>no</b> messages} =1 {One {{message}}}}after</div> ` ,
2019-02-08 17:10:19 -05:00
'TestComp' , { tokenizeExpansionForms : true } ) ;
2016-04-12 14:46:39 -04:00
2016-06-09 17:53:03 -04:00
expect ( humanizeDom ( parsed ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'div' , 0 ] ,
[ html . Text , 'before' , 1 ] ,
[ html . Expansion , 'messages.length' , 'plural' , 1 ] ,
[ html . ExpansionCase , '=0' , 2 ] ,
[ html . ExpansionCase , '=1' , 2 ] ,
[ html . Text , 'after' , 1 ] ,
2016-06-09 17:53:03 -04:00
] ) ;
2016-11-12 08:08:58 -05:00
const cases = ( < any > parsed . rootNodes [ 0 ] ) . children [ 1 ] . cases ;
2016-04-12 14:46:39 -04:00
2016-07-21 16:56:58 -04:00
expect ( humanizeDom ( new ParseTreeResult ( cases [ 0 ] . expression , [ ] ) ) ) . toEqual ( [
[ html . Text , 'You have ' , 0 ] ,
[ html . Element , 'b' , 0 ] ,
[ html . Text , 'no' , 1 ] ,
[ html . Text , ' messages' , 0 ] ,
2016-06-08 19:38:52 -04:00
] ) ;
2016-04-12 14:46:39 -04:00
2020-04-08 13:14:18 -04:00
expect ( humanizeDom ( new ParseTreeResult ( cases [ 1 ] . expression , [ ] ) ) ) . toEqual ( [
[ html . Text , 'One {{message}}' , 0 ]
] ) ;
2016-04-12 14:46:39 -04:00
} ) ;
2016-12-16 18:33:16 -05:00
it ( 'should parse out expansion forms' , ( ) = > {
2019-02-08 17:10:19 -05:00
const parsed = parser . parse (
` <div><span>{a, plural, =0 {b}}</span></div> ` , 'TestComp' ,
{ tokenizeExpansionForms : true } ) ;
2016-12-16 18:33:16 -05:00
expect ( humanizeDom ( parsed ) ) . toEqual ( [
[ html . Element , 'div' , 0 ] ,
[ html . Element , 'span' , 1 ] ,
[ html . Expansion , 'a' , 'plural' , 2 ] ,
[ html . ExpansionCase , '=0' , 3 ] ,
] ) ;
} ) ;
2016-06-08 19:38:52 -04:00
it ( 'should parse out nested expansion forms' , ( ) = > {
2016-11-12 08:08:58 -05:00
const parsed = parser . parse (
2019-02-08 17:10:19 -05:00
` {messages.length, plural, =0 { {p.gender, select, male {m}} }} ` , 'TestComp' ,
{ tokenizeExpansionForms : true } ) ;
2016-06-09 17:53:03 -04:00
expect ( humanizeDom ( parsed ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Expansion , 'messages.length' , 'plural' , 0 ] ,
[ html . ExpansionCase , '=0' , 1 ] ,
2016-06-09 17:53:03 -04:00
] ) ;
2016-04-13 19:01:25 -04:00
2016-11-12 08:08:58 -05:00
const firstCase = ( < any > parsed . rootNodes [ 0 ] ) . cases [ 0 ] ;
2016-04-13 19:01:25 -04:00
2016-07-21 16:56:58 -04:00
expect ( humanizeDom ( new ParseTreeResult ( firstCase . expression , [ ] ) ) ) . toEqual ( [
2016-11-04 18:10:19 -04:00
[ html . Expansion , 'p.gender' , 'select' , 0 ] ,
[ html . ExpansionCase , 'male' , 1 ] ,
2016-07-21 16:56:58 -04:00
[ html . Text , ' ' , 0 ] ,
2016-06-09 17:53:03 -04:00
] ) ;
2016-04-13 19:01:25 -04:00
} ) ;
2016-06-08 19:38:52 -04:00
it ( 'should error when expansion form is not closed' , ( ) = > {
2019-02-08 17:10:19 -05:00
const p = parser . parse (
` {messages.length, plural, =0 {one} ` , 'TestComp' , { tokenizeExpansionForms : true } ) ;
2016-06-08 19:38:52 -04:00
expect ( humanizeErrors ( p . errors ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ null , 'Invalid ICU message. Missing \'}\'.' , '0:34' ]
2016-06-08 19:38:52 -04:00
] ) ;
2016-04-12 14:46:39 -04:00
} ) ;
2017-07-13 07:55:15 -04:00
it ( 'should support ICU expressions with cases that contain numbers' , ( ) = > {
2019-02-08 17:10:19 -05:00
const p = parser . parse (
` {sex, select, male {m} female {f} 0 {other}} ` , 'TestComp' ,
{ tokenizeExpansionForms : true } ) ;
2017-07-13 07:55:15 -04:00
expect ( p . errors . length ) . toEqual ( 0 ) ;
} ) ;
2020-03-18 07:39:48 -04:00
it ( ` should support ICU expressions with cases that contain any character except '}' ` ,
( ) = > {
const p = parser . parse (
` {a, select, b {foo} % bar {% bar}} ` , 'TestComp' , { tokenizeExpansionForms : true } ) ;
expect ( p . errors . length ) . toEqual ( 0 ) ;
} ) ;
it ( 'should error when expansion case is not properly closed' , ( ) = > {
const p = parser . parse (
` {a, select, b {foo} % { bar {% bar}} ` , 'TestComp' , { tokenizeExpansionForms : true } ) ;
expect ( humanizeErrors ( p . errors ) ) . toEqual ( [
[
6 ,
'Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ \'{\' }}") to escape it.)' ,
'0:36'
] ,
[ null , 'Invalid ICU message. Missing \'}\'.' , '0:22' ]
] ) ;
} ) ;
2016-06-08 19:38:52 -04:00
it ( 'should error when expansion case is not closed' , ( ) = > {
2019-02-08 17:10:19 -05:00
const p = parser . parse (
` {messages.length, plural, =0 {one ` , 'TestComp' , { tokenizeExpansionForms : true } ) ;
2016-06-08 19:38:52 -04:00
expect ( humanizeErrors ( p . errors ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ null , 'Invalid ICU message. Missing \'}\'.' , '0:29' ]
2016-06-08 19:38:52 -04:00
] ) ;
2016-04-12 14:46:39 -04:00
} ) ;
2016-06-08 19:38:52 -04:00
it ( 'should error when invalid html in the case' , ( ) = > {
2019-02-08 17:10:19 -05:00
const p = parser . parse (
` {messages.length, plural, =0 {<b/>} ` , 'TestComp' , { tokenizeExpansionForms : true } ) ;
2016-06-08 19:38:52 -04:00
expect ( humanizeErrors ( p . errors ) ) . toEqual ( [
[ 'b' , 'Only void and foreign elements can be self closed "b"' , '0:30' ]
] ) ;
2016-04-12 14:46:39 -04:00
} ) ;
} ) ;
2015-11-10 18:56:25 -05:00
describe ( 'source spans' , ( ) = > {
it ( 'should store the location' , ( ) = > {
expect ( humanizeDomSourceSpans ( parser . parse (
'<div [prop]="v1" (e)="do()" attr="v2" noValue>\na\n</div>' , 'TestComp' ) ) )
. toEqual ( [
2016-07-21 16:56:58 -04:00
[ html . Element , 'div' , 0 , '<div [prop]="v1" (e)="do()" attr="v2" noValue>' ] ,
[ html . Attribute , '[prop]' , 'v1' , '[prop]="v1"' ] ,
[ html . Attribute , '(e)' , 'do()' , '(e)="do()"' ] ,
[ html . Attribute , 'attr' , 'v2' , 'attr="v2"' ] ,
[ html . Attribute , 'noValue' , '' , 'noValue' ] ,
[ html . Text , '\na\n' , 1 , '\na\n' ] ,
2015-11-10 18:56:25 -05:00
] ) ;
} ) ;
2016-03-23 16:43:28 -04:00
it ( 'should set the start and end source spans' , ( ) = > {
2016-11-12 08:08:58 -05:00
const node = < html.Element > parser . parse ( '<div>a</div>' , 'TestComp' ) . rootNodes [ 0 ] ;
2016-03-23 16:43:28 -04:00
2020-04-08 13:14:18 -04:00
expect ( node . startSourceSpan ! . start . offset ) . toEqual ( 0 ) ;
expect ( node . startSourceSpan ! . end . offset ) . toEqual ( 5 ) ;
2016-03-23 16:43:28 -04:00
2020-04-08 13:14:18 -04:00
expect ( node . endSourceSpan ! . start . offset ) . toEqual ( 6 ) ;
expect ( node . endSourceSpan ! . end . offset ) . toEqual ( 12 ) ;
2016-03-23 16:43:28 -04:00
} ) ;
2016-07-21 16:56:58 -04:00
it ( 'should support expansion form' , ( ) = > {
2019-02-08 17:10:19 -05:00
expect ( humanizeDomSourceSpans ( parser . parse (
'<div>{count, plural, =0 {msg}}</div>' , 'TestComp' ,
{ tokenizeExpansionForms : true } ) ) )
2016-07-21 16:56:58 -04:00
. toEqual ( [
[ html . Element , 'div' , 0 , '<div>' ] ,
[ html . Expansion , 'count' , 'plural' , 1 , '{count, plural, =0 {msg}}' ] ,
[ html . ExpansionCase , '=0' , 2 , '=0 {msg}' ] ,
] ) ;
} ) ;
2016-10-07 16:53:29 -04:00
it ( 'should not report a value span for an attribute without a value' , ( ) = > {
const ast = parser . parse ( '<div bar></div>' , 'TestComp' ) ;
expect ( ( ast . rootNodes [ 0 ] as html . Element ) . attrs [ 0 ] . valueSpan ) . toBeUndefined ( ) ;
} ) ;
2017-07-07 19:55:17 -04:00
it ( 'should report a value span for an attribute with a value' , ( ) = > {
2016-10-07 16:53:29 -04:00
const ast = parser . parse ( '<div bar="12"></div>' , 'TestComp' ) ;
const attr = ( ast . rootNodes [ 0 ] as html . Element ) . attrs [ 0 ] ;
2020-04-08 13:14:18 -04:00
expect ( attr . valueSpan ! . start . offset ) . toEqual ( 10 ) ;
expect ( attr . valueSpan ! . end . offset ) . toEqual ( 12 ) ;
2019-02-08 17:10:20 -05:00
} ) ;
it ( 'should report a value span for an unquoted attribute value' , ( ) = > {
const ast = parser . parse ( '<div bar=12></div>' , 'TestComp' ) ;
const attr = ( ast . rootNodes [ 0 ] as html . Element ) . attrs [ 0 ] ;
2020-04-08 13:14:18 -04:00
expect ( attr . valueSpan ! . start . offset ) . toEqual ( 9 ) ;
expect ( attr . valueSpan ! . end . offset ) . toEqual ( 11 ) ;
2016-10-07 16:53:29 -04:00
} ) ;
2015-11-10 18:56:25 -05:00
} ) ;
2016-10-10 12:13:50 -04:00
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 ( '<div></div>' , 'TestComp' ) ) ;
expect ( result ) . toEqual ( [ [ html . Element , 'div' , 0 ] ] ) ;
} ) ;
it ( 'should visit attribute nodes' , ( ) = > {
const result = humanizeDom ( parser . parse ( '<div id="foo"></div>' , 'TestComp' ) ) ;
expect ( result ) . toContain ( [ html . Attribute , 'id' , 'foo' ] ) ;
} ) ;
it ( 'should visit all nodes' , ( ) = > {
const result =
parser . parse ( '<div id="foo"><span id="bar">a</span><span>b</span></div>' , 'TestComp' ) ;
const accumulator : html.Node [ ] = [ ] ;
const visitor = new class {
2020-04-08 13:14:18 -04:00
visit ( node : html.Node , context : any ) {
accumulator . push ( node ) ;
}
2016-10-10 12:13:50 -04:00
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 {
2020-04-08 13:14:18 -04:00
visit ( node : html.Node , context : any ) {
return true ;
}
visitElement ( element : html.Element , context : any ) : any {
throw Error ( 'Unexpected' ) ;
}
2016-10-10 12:13:50 -04:00
visitAttribute ( attribute : html.Attribute , context : any ) : any {
throw Error ( 'Unexpected' ) ;
}
2020-04-08 13:14:18 -04:00
visitText ( text : html.Text , context : any ) : any {
throw Error ( 'Unexpected' ) ;
}
visitComment ( comment : html.Comment , context : any ) : any {
throw Error ( 'Unexpected' ) ;
}
2016-10-10 12:13:50 -04:00
visitExpansion ( expansion : html.Expansion , context : any ) : any {
throw Error ( 'Unexpected' ) ;
}
visitExpansionCase ( expansionCase : html.ExpansionCase , context : any ) : any {
throw Error ( 'Unexpected' ) ;
}
} ;
const result = parser . parse ( '<div id="foo"></div><div id="bar"></div>' , 'TestComp' ) ;
const traversal = html . visitAll ( visitor , result . rootNodes ) ;
expect ( traversal ) . toEqual ( [ true , true ] ) ;
} ) ;
} ) ;
2015-11-10 18:56:25 -05:00
describe ( 'errors' , ( ) = > {
it ( 'should report unexpected closing tags' , ( ) = > {
2016-11-12 08:08:58 -05:00
const errors = parser . parse ( '<div></p></div>' , 'TestComp' ) . errors ;
2015-11-10 18:56:25 -05:00
expect ( errors . length ) . toEqual ( 1 ) ;
2017-03-10 16:05:17 -05:00
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'
] ] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
2016-06-03 13:49:17 -04:00
it ( 'should report subsequent open tags without proper close tag' , ( ) = > {
2016-11-12 08:08:58 -05:00
const errors = parser . parse ( '<div</div>' , 'TestComp' ) . errors ;
2016-06-03 13:49:17 -04:00
expect ( errors . length ) . toEqual ( 1 ) ;
2017-03-10 16:05:17 -05:00
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'
] ] ) ;
2016-06-03 13:49:17 -04:00
} ) ;
2015-12-03 18:53:44 -05:00
it ( 'should report closing tag for void elements' , ( ) = > {
2016-11-12 08:08:58 -05:00
const errors = parser . parse ( '<input></input>' , 'TestComp' ) . errors ;
2015-12-02 13:11:01 -05:00
expect ( errors . length ) . toEqual ( 1 ) ;
2016-06-08 19:38:52 -04:00
expect ( humanizeErrors ( errors ) ) . toEqual ( [
[ 'input' , 'Void elements do not have end tags "input"' , '0:7' ]
] ) ;
2015-12-02 13:11:01 -05:00
} ) ;
2015-12-03 19:10:20 -05:00
it ( 'should report self closing html element' , ( ) = > {
2016-11-12 08:08:58 -05:00
const errors = parser . parse ( '<p />' , 'TestComp' ) . errors ;
2015-12-03 19:10:20 -05:00
expect ( errors . length ) . toEqual ( 1 ) ;
2016-06-08 19:38:52 -04:00
expect ( humanizeErrors ( errors ) ) . toEqual ( [
[ 'p' , 'Only void and foreign elements can be self closed "p"' , '0:0' ]
] ) ;
2015-12-03 19:10:20 -05:00
} ) ;
it ( 'should report self closing custom element' , ( ) = > {
2016-11-12 08:08:58 -05:00
const errors = parser . parse ( '<my-cmp />' , 'TestComp' ) . errors ;
2015-12-03 19:10:20 -05:00
expect ( errors . length ) . toEqual ( 1 ) ;
2016-06-08 19:38:52 -04:00
expect ( humanizeErrors ( errors ) ) . toEqual ( [
[ 'my-cmp' , 'Only void and foreign elements can be self closed "my-cmp"' , '0:0' ]
] ) ;
2015-12-03 19:10:20 -05:00
} ) ;
2015-11-10 18:56:25 -05:00
it ( 'should also report lexer errors' , ( ) = > {
2016-11-12 08:08:58 -05:00
const errors = parser . parse ( '<!-err--><div></p></div>' , 'TestComp' ) . errors ;
2015-11-10 18:56:25 -05:00
expect ( errors . length ) . toEqual ( 2 ) ;
2016-06-08 19:38:52 -04:00
expect ( humanizeErrors ( errors ) ) . toEqual ( [
2016-07-21 16:56:58 -04:00
[ TokenType . COMMENT_START , 'Unexpected character "e"' , '0:3' ] ,
2017-03-10 16:05:17 -05:00
[
'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'
]
2016-06-08 19:38:52 -04:00
] ) ;
2015-11-10 18:56:25 -05:00
} ) ;
2015-08-25 18:36:02 -04:00
} ) ;
} ) ;
} ) ;
}
2016-03-23 16:43:28 -04:00
export function humanizeErrors ( errors : ParseError [ ] ) : any [ ] {
2016-06-22 20:25:42 -04:00
return errors . map ( e = > {
2016-07-21 16:56:58 -04:00
if ( e instanceof TreeError ) {
2015-11-10 18:56:25 -05:00
// Parser errors
2016-06-22 20:25:42 -04:00
return [ < any > e . elementName , e . msg , humanizeLineColumn ( e . span . start ) ] ;
2015-11-10 18:56:25 -05:00
}
// Tokenizer errors
2016-06-22 20:25:42 -04:00
return [ ( < any > e ) . tokenType , e . msg , humanizeLineColumn ( e . span . start ) ] ;
2015-11-10 18:56:25 -05:00
} ) ;
2016-04-28 20:50:03 -04:00
}