parse.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import {
  2. baseParse as parse,
  3. NodeTypes,
  4. ElementNode,
  5. TextNode,
  6. ErrorCodes,
  7. ElementTypes,
  8. InterpolationNode
  9. } from '@vue/compiler-core'
  10. import {
  11. parserOptionsMinimal as parserOptions,
  12. DOMNamespaces
  13. } from '../src/parserOptionsMinimal'
  14. describe('DOM parser', () => {
  15. describe('Text', () => {
  16. test('textarea handles comments/elements as just text', () => {
  17. const ast = parse(
  18. '<textarea>some<div>text</div>and<!--comment--></textarea>',
  19. parserOptions
  20. )
  21. const element = ast.children[0] as ElementNode
  22. const text = element.children[0] as TextNode
  23. expect(text).toStrictEqual({
  24. type: NodeTypes.TEXT,
  25. content: 'some<div>text</div>and<!--comment-->',
  26. loc: {
  27. start: { offset: 10, line: 1, column: 11 },
  28. end: { offset: 46, line: 1, column: 47 },
  29. source: 'some<div>text</div>and<!--comment-->'
  30. }
  31. })
  32. })
  33. test('textarea handles character references', () => {
  34. const ast = parse('<textarea>&amp;</textarea>', parserOptions)
  35. const element = ast.children[0] as ElementNode
  36. const text = element.children[0] as TextNode
  37. expect(text).toStrictEqual({
  38. type: NodeTypes.TEXT,
  39. content: '&',
  40. loc: {
  41. start: { offset: 10, line: 1, column: 11 },
  42. end: { offset: 15, line: 1, column: 16 },
  43. source: '&amp;'
  44. }
  45. })
  46. })
  47. test('style handles comments/elements as just a text', () => {
  48. const ast = parse(
  49. '<style>some<div>text</div>and<!--comment--></style>',
  50. parserOptions
  51. )
  52. const element = ast.children[0] as ElementNode
  53. const text = element.children[0] as TextNode
  54. expect(text).toStrictEqual({
  55. type: NodeTypes.TEXT,
  56. content: 'some<div>text</div>and<!--comment-->',
  57. loc: {
  58. start: { offset: 7, line: 1, column: 8 },
  59. end: { offset: 43, line: 1, column: 44 },
  60. source: 'some<div>text</div>and<!--comment-->'
  61. }
  62. })
  63. })
  64. test("style doesn't handle character references", () => {
  65. const ast = parse('<style>&amp;</style>', parserOptions)
  66. const element = ast.children[0] as ElementNode
  67. const text = element.children[0] as TextNode
  68. expect(text).toStrictEqual({
  69. type: NodeTypes.TEXT,
  70. content: '&amp;',
  71. loc: {
  72. start: { offset: 7, line: 1, column: 8 },
  73. end: { offset: 12, line: 1, column: 13 },
  74. source: '&amp;'
  75. }
  76. })
  77. })
  78. test('CDATA', () => {
  79. const ast = parse('<svg><![CDATA[some text]]></svg>', parserOptions)
  80. const text = (ast.children[0] as ElementNode).children![0] as TextNode
  81. expect(text).toStrictEqual({
  82. type: NodeTypes.TEXT,
  83. content: 'some text',
  84. loc: {
  85. start: { offset: 14, line: 1, column: 15 },
  86. end: { offset: 23, line: 1, column: 24 },
  87. source: 'some text'
  88. }
  89. })
  90. })
  91. test('<pre> tag should preserve raw whitespace', () => {
  92. const rawText = ` \na b \n c`
  93. const ast = parse(`<pre>${rawText}</pre>`, parserOptions)
  94. expect((ast.children[0] as ElementNode).children[0]).toMatchObject({
  95. type: NodeTypes.TEXT,
  96. content: rawText
  97. })
  98. })
  99. })
  100. describe('Interpolation', () => {
  101. test('HTML entities in interpolation should be translated for backward compatibility.', () => {
  102. const ast = parse('<div>{{ a &lt; b }}</div>', parserOptions)
  103. const element = ast.children[0] as ElementNode
  104. const interpolation = element.children[0] as InterpolationNode
  105. expect(interpolation).toStrictEqual({
  106. type: NodeTypes.INTERPOLATION,
  107. content: {
  108. type: NodeTypes.SIMPLE_EXPRESSION,
  109. content: `a < b`,
  110. isStatic: false,
  111. isConstant: false,
  112. loc: {
  113. start: { offset: 8, line: 1, column: 9 },
  114. end: { offset: 16, line: 1, column: 17 },
  115. source: 'a &lt; b'
  116. }
  117. },
  118. loc: {
  119. start: { offset: 5, line: 1, column: 6 },
  120. end: { offset: 19, line: 1, column: 20 },
  121. source: '{{ a &lt; b }}'
  122. }
  123. })
  124. })
  125. })
  126. describe('Element', () => {
  127. test('void element', () => {
  128. const ast = parse('<img>after', parserOptions)
  129. const element = ast.children[0] as ElementNode
  130. expect(element).toStrictEqual({
  131. type: NodeTypes.ELEMENT,
  132. ns: DOMNamespaces.HTML,
  133. tag: 'img',
  134. tagType: ElementTypes.ELEMENT,
  135. props: [],
  136. isSelfClosing: false,
  137. children: [],
  138. loc: {
  139. start: { offset: 0, line: 1, column: 1 },
  140. end: { offset: 5, line: 1, column: 6 },
  141. source: '<img>'
  142. },
  143. codegenNode: undefined
  144. })
  145. })
  146. test('native element', () => {
  147. const ast = parse('<div></div><comp></comp><Comp></Comp>', parserOptions)
  148. expect(ast.children[0]).toMatchObject({
  149. type: NodeTypes.ELEMENT,
  150. tag: 'div',
  151. tagType: ElementTypes.ELEMENT
  152. })
  153. expect(ast.children[1]).toMatchObject({
  154. type: NodeTypes.ELEMENT,
  155. tag: 'comp',
  156. tagType: ElementTypes.COMPONENT
  157. })
  158. expect(ast.children[2]).toMatchObject({
  159. type: NodeTypes.ELEMENT,
  160. tag: 'Comp',
  161. tagType: ElementTypes.COMPONENT
  162. })
  163. })
  164. test('Strict end tag detection for textarea.', () => {
  165. const ast = parse(
  166. '<textarea>hello</textarea</textarea0></texTArea a="<>">',
  167. {
  168. ...parserOptions,
  169. onError: err => {
  170. if (err.code !== ErrorCodes.END_TAG_WITH_ATTRIBUTES) {
  171. throw err
  172. }
  173. }
  174. }
  175. )
  176. const element = ast.children[0] as ElementNode
  177. const text = element.children[0] as TextNode
  178. expect(ast.children.length).toBe(1)
  179. expect(text).toStrictEqual({
  180. type: NodeTypes.TEXT,
  181. content: 'hello</textarea</textarea0>',
  182. loc: {
  183. start: { offset: 10, line: 1, column: 11 },
  184. end: { offset: 37, line: 1, column: 38 },
  185. source: 'hello</textarea</textarea0>'
  186. }
  187. })
  188. })
  189. })
  190. describe('Namespaces', () => {
  191. test('HTML namespace', () => {
  192. const ast = parse('<html>test</html>', parserOptions)
  193. const element = ast.children[0] as ElementNode
  194. expect(element.ns).toBe(DOMNamespaces.HTML)
  195. })
  196. test('SVG namespace', () => {
  197. const ast = parse('<svg>test</svg>', parserOptions)
  198. const element = ast.children[0] as ElementNode
  199. expect(element.ns).toBe(DOMNamespaces.SVG)
  200. })
  201. test('MATH_ML namespace', () => {
  202. const ast = parse('<math>test</math>', parserOptions)
  203. const element = ast.children[0] as ElementNode
  204. expect(element.ns).toBe(DOMNamespaces.MATH_ML)
  205. })
  206. test('SVG in MATH_ML namespace', () => {
  207. const ast = parse(
  208. '<math><annotation-xml><svg></svg></annotation-xml></math>',
  209. parserOptions
  210. )
  211. const elementMath = ast.children[0] as ElementNode
  212. const elementAnnotation = elementMath.children[0] as ElementNode
  213. const elementSvg = elementAnnotation.children[0] as ElementNode
  214. expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
  215. expect(elementSvg.ns).toBe(DOMNamespaces.SVG)
  216. })
  217. test('html text/html in MATH_ML namespace', () => {
  218. const ast = parse(
  219. '<math><annotation-xml encoding="text/html"><test/></annotation-xml></math>',
  220. parserOptions
  221. )
  222. const elementMath = ast.children[0] as ElementNode
  223. const elementAnnotation = elementMath.children[0] as ElementNode
  224. const element = elementAnnotation.children[0] as ElementNode
  225. expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
  226. expect(element.ns).toBe(DOMNamespaces.HTML)
  227. })
  228. test('html application/xhtml+xml in MATH_ML namespace', () => {
  229. const ast = parse(
  230. '<math><annotation-xml encoding="application/xhtml+xml"><test/></annotation-xml></math>',
  231. parserOptions
  232. )
  233. const elementMath = ast.children[0] as ElementNode
  234. const elementAnnotation = elementMath.children[0] as ElementNode
  235. const element = elementAnnotation.children[0] as ElementNode
  236. expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
  237. expect(element.ns).toBe(DOMNamespaces.HTML)
  238. })
  239. test('mtext malignmark in MATH_ML namespace', () => {
  240. const ast = parse(
  241. '<math><mtext><malignmark/></mtext></math>',
  242. parserOptions
  243. )
  244. const elementMath = ast.children[0] as ElementNode
  245. const elementText = elementMath.children[0] as ElementNode
  246. const element = elementText.children[0] as ElementNode
  247. expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
  248. expect(element.ns).toBe(DOMNamespaces.MATH_ML)
  249. })
  250. test('mtext and not malignmark tag in MATH_ML namespace', () => {
  251. const ast = parse('<math><mtext><test/></mtext></math>', parserOptions)
  252. const elementMath = ast.children[0] as ElementNode
  253. const elementText = elementMath.children[0] as ElementNode
  254. const element = elementText.children[0] as ElementNode
  255. expect(elementMath.ns).toBe(DOMNamespaces.MATH_ML)
  256. expect(element.ns).toBe(DOMNamespaces.HTML)
  257. })
  258. test('foreignObject tag in SVG namespace', () => {
  259. const ast = parse(
  260. '<svg><foreignObject><test/></foreignObject></svg>',
  261. parserOptions
  262. )
  263. const elementSvg = ast.children[0] as ElementNode
  264. const elementForeignObject = elementSvg.children[0] as ElementNode
  265. const element = elementForeignObject.children[0] as ElementNode
  266. expect(elementSvg.ns).toBe(DOMNamespaces.SVG)
  267. expect(element.ns).toBe(DOMNamespaces.HTML)
  268. })
  269. test('desc tag in SVG namespace', () => {
  270. const ast = parse('<svg><desc><test/></desc></svg>', parserOptions)
  271. const elementSvg = ast.children[0] as ElementNode
  272. const elementDesc = elementSvg.children[0] as ElementNode
  273. const element = elementDesc.children[0] as ElementNode
  274. expect(elementSvg.ns).toBe(DOMNamespaces.SVG)
  275. expect(element.ns).toBe(DOMNamespaces.HTML)
  276. })
  277. test('title tag in SVG namespace', () => {
  278. const ast = parse('<svg><title><test/></title></svg>', parserOptions)
  279. const elementSvg = ast.children[0] as ElementNode
  280. const elementTitle = elementSvg.children[0] as ElementNode
  281. const element = elementTitle.children[0] as ElementNode
  282. expect(elementSvg.ns).toBe(DOMNamespaces.SVG)
  283. expect(element.ns).toBe(DOMNamespaces.HTML)
  284. })
  285. test('SVG in HTML namespace', () => {
  286. const ast = parse('<html><svg></svg></html>', parserOptions)
  287. const elementHtml = ast.children[0] as ElementNode
  288. const element = elementHtml.children[0] as ElementNode
  289. expect(elementHtml.ns).toBe(DOMNamespaces.HTML)
  290. expect(element.ns).toBe(DOMNamespaces.SVG)
  291. })
  292. test('MATH in HTML namespace', () => {
  293. const ast = parse('<html><math></math></html>', parserOptions)
  294. const elementHtml = ast.children[0] as ElementNode
  295. const element = elementHtml.children[0] as ElementNode
  296. expect(elementHtml.ns).toBe(DOMNamespaces.HTML)
  297. expect(element.ns).toBe(DOMNamespaces.MATH_ML)
  298. })
  299. })
  300. })