|
|
@ -1796,166 +1796,167 @@ describe('compiler: parse', () => { |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
test('self closing single tag', () => { |
|
|
|
const ast = baseParse('<div :class="{ some: condition }" />') |
|
|
|
describe('Edge Cases', () => { |
|
|
|
test('self closing single tag', () => { |
|
|
|
const ast = baseParse('<div :class="{ some: condition }" />') |
|
|
|
|
|
|
|
expect(ast.children).toHaveLength(1) |
|
|
|
expect(ast.children[0]).toMatchObject({ tag: 'div' }) |
|
|
|
}) |
|
|
|
expect(ast.children).toHaveLength(1) |
|
|
|
expect(ast.children[0]).toMatchObject({ tag: 'div' }) |
|
|
|
}) |
|
|
|
|
|
|
|
test('self closing multiple tag', () => { |
|
|
|
const ast = baseParse( |
|
|
|
`<div :class="{ some: condition }" />\n` + |
|
|
|
`<p v-bind:style="{ color: 'red' }"/>` |
|
|
|
) |
|
|
|
test('self closing multiple tag', () => { |
|
|
|
const ast = baseParse( |
|
|
|
`<div :class="{ some: condition }" />\n` + |
|
|
|
`<p v-bind:style="{ color: 'red' }"/>` |
|
|
|
) |
|
|
|
|
|
|
|
expect(ast).toMatchSnapshot() |
|
|
|
expect(ast).toMatchSnapshot() |
|
|
|
|
|
|
|
expect(ast.children).toHaveLength(2) |
|
|
|
expect(ast.children[0]).toMatchObject({ tag: 'div' }) |
|
|
|
expect(ast.children[1]).toMatchObject({ tag: 'p' }) |
|
|
|
}) |
|
|
|
expect(ast.children).toHaveLength(2) |
|
|
|
expect(ast.children[0]).toMatchObject({ tag: 'div' }) |
|
|
|
expect(ast.children[1]).toMatchObject({ tag: 'p' }) |
|
|
|
}) |
|
|
|
|
|
|
|
test('valid html', () => { |
|
|
|
const ast = baseParse( |
|
|
|
`<div :class="{ some: condition }">\n` + |
|
|
|
` <p v-bind:style="{ color: 'red' }"/>\n` + |
|
|
|
` <!-- a comment with <html> inside it -->\n` + |
|
|
|
`</div>` |
|
|
|
) |
|
|
|
test('valid html', () => { |
|
|
|
const ast = baseParse( |
|
|
|
`<div :class="{ some: condition }">\n` + |
|
|
|
` <p v-bind:style="{ color: 'red' }"/>\n` + |
|
|
|
` <!-- a comment with <html> inside it -->\n` + |
|
|
|
`</div>` |
|
|
|
) |
|
|
|
|
|
|
|
expect(ast).toMatchSnapshot() |
|
|
|
expect(ast).toMatchSnapshot() |
|
|
|
|
|
|
|
expect(ast.children).toHaveLength(1) |
|
|
|
const el = ast.children[0] as any |
|
|
|
expect(el).toMatchObject({ |
|
|
|
tag: 'div' |
|
|
|
}) |
|
|
|
expect(el.children).toHaveLength(2) |
|
|
|
expect(el.children[0]).toMatchObject({ |
|
|
|
tag: 'p' |
|
|
|
}) |
|
|
|
expect(el.children[1]).toMatchObject({ |
|
|
|
type: NodeTypes.COMMENT |
|
|
|
expect(ast.children).toHaveLength(1) |
|
|
|
const el = ast.children[0] as any |
|
|
|
expect(el).toMatchObject({ |
|
|
|
tag: 'div' |
|
|
|
}) |
|
|
|
expect(el.children).toHaveLength(2) |
|
|
|
expect(el.children[0]).toMatchObject({ |
|
|
|
tag: 'p' |
|
|
|
}) |
|
|
|
expect(el.children[1]).toMatchObject({ |
|
|
|
type: NodeTypes.COMMENT |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
test('invalid html', () => { |
|
|
|
expect(() => { |
|
|
|
baseParse(`<div>\n<span>\n</div>\n</span>`) |
|
|
|
}).toThrow('Element is missing end tag.') |
|
|
|
test('invalid html', () => { |
|
|
|
expect(() => { |
|
|
|
baseParse(`<div>\n<span>\n</div>\n</span>`) |
|
|
|
}).toThrow('Element is missing end tag.') |
|
|
|
|
|
|
|
const spy = vi.fn() |
|
|
|
const ast = baseParse(`<div>\n<span>\n</div>\n</span>`, { |
|
|
|
onError: spy |
|
|
|
}) |
|
|
|
const spy = vi.fn() |
|
|
|
const ast = baseParse(`<div>\n<span>\n</div>\n</span>`, { |
|
|
|
onError: spy |
|
|
|
}) |
|
|
|
|
|
|
|
expect(spy.mock.calls).toMatchObject([ |
|
|
|
[ |
|
|
|
{ |
|
|
|
code: ErrorCodes.X_MISSING_END_TAG, |
|
|
|
loc: { |
|
|
|
start: { |
|
|
|
offset: 6, |
|
|
|
line: 2, |
|
|
|
column: 1 |
|
|
|
expect(spy.mock.calls).toMatchObject([ |
|
|
|
[ |
|
|
|
{ |
|
|
|
code: ErrorCodes.X_MISSING_END_TAG, |
|
|
|
loc: { |
|
|
|
start: { |
|
|
|
offset: 6, |
|
|
|
line: 2, |
|
|
|
column: 1 |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
], |
|
|
|
[ |
|
|
|
{ |
|
|
|
code: ErrorCodes.X_INVALID_END_TAG, |
|
|
|
loc: { |
|
|
|
start: { |
|
|
|
offset: 20, |
|
|
|
line: 4, |
|
|
|
column: 1 |
|
|
|
], |
|
|
|
[ |
|
|
|
{ |
|
|
|
code: ErrorCodes.X_INVALID_END_TAG, |
|
|
|
loc: { |
|
|
|
start: { |
|
|
|
offset: 20, |
|
|
|
line: 4, |
|
|
|
column: 1 |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
] |
|
|
|
]) |
|
|
|
|
|
|
|
expect(ast).toMatchSnapshot() |
|
|
|
}) |
|
|
|
|
|
|
|
test('parse with correct location info', () => { |
|
|
|
const fooSrc = `foo
|
|
|
|
is `
|
|
|
|
const barSrc = `{{ bar }}` |
|
|
|
const butSrc = ` but ` |
|
|
|
const bazSrc = `{{ baz }}` |
|
|
|
const [foo, bar, but, baz] = baseParse( |
|
|
|
fooSrc + barSrc + butSrc + bazSrc |
|
|
|
).children |
|
|
|
|
|
|
|
let offset = 0 |
|
|
|
expect(foo.loc.start).toEqual({ line: 1, column: 1, offset }) |
|
|
|
offset += fooSrc.length |
|
|
|
expect(foo.loc.end).toEqual({ line: 2, column: 5, offset }) |
|
|
|
|
|
|
|
expect(bar.loc.start).toEqual({ line: 2, column: 5, offset }) |
|
|
|
const barInner = (bar as InterpolationNode).content |
|
|
|
offset += 3 |
|
|
|
expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset }) |
|
|
|
offset += 3 |
|
|
|
expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset }) |
|
|
|
offset += 3 |
|
|
|
expect(bar.loc.end).toEqual({ line: 2, column: 14, offset }) |
|
|
|
|
|
|
|
expect(but.loc.start).toEqual({ line: 2, column: 14, offset }) |
|
|
|
offset += butSrc.length |
|
|
|
expect(but.loc.end).toEqual({ line: 2, column: 19, offset }) |
|
|
|
|
|
|
|
expect(baz.loc.start).toEqual({ line: 2, column: 19, offset }) |
|
|
|
const bazInner = (baz as InterpolationNode).content |
|
|
|
offset += 3 |
|
|
|
expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset }) |
|
|
|
offset += 3 |
|
|
|
expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset }) |
|
|
|
offset += 3 |
|
|
|
expect(baz.loc.end).toEqual({ line: 2, column: 28, offset }) |
|
|
|
}) |
|
|
|
] |
|
|
|
]) |
|
|
|
|
|
|
|
// With standard HTML parsing, the following input would ignore the slash
|
|
|
|
// and treat "<" and "template" as attributes on the open tag of "Hello",
|
|
|
|
// causing `<template>` to fail to close, and `<script>` being parsed as its
|
|
|
|
// child. This is would never be intended in actual templates, but is a common
|
|
|
|
// intermediate state from user input when parsing for IDE support. We want
|
|
|
|
// the `<script>` to be at root-level to keep the SFC structure stable for
|
|
|
|
// Volar to do incremental computations.
|
|
|
|
test('tag termination handling for IDE', () => { |
|
|
|
const spy = vi.fn() |
|
|
|
const ast = baseParse( |
|
|
|
`<template><Hello\n</template><script>console.log(1)</script>`, |
|
|
|
{ |
|
|
|
onError: spy |
|
|
|
} |
|
|
|
) |
|
|
|
//
|
|
|
|
expect(ast.children.length).toBe(2) |
|
|
|
expect(ast.children[1]).toMatchObject({ |
|
|
|
type: NodeTypes.ELEMENT, |
|
|
|
tag: 'script' |
|
|
|
expect(ast).toMatchSnapshot() |
|
|
|
}) |
|
|
|
|
|
|
|
test('parse with correct location info', () => { |
|
|
|
const fooSrc = `foo\n is ` |
|
|
|
const barSrc = `{{ bar }}` |
|
|
|
const butSrc = ` but ` |
|
|
|
const bazSrc = `{{ baz }}` |
|
|
|
const [foo, bar, but, baz] = baseParse( |
|
|
|
fooSrc + barSrc + butSrc + bazSrc |
|
|
|
).children |
|
|
|
|
|
|
|
let offset = 0 |
|
|
|
expect(foo.loc.start).toEqual({ line: 1, column: 1, offset }) |
|
|
|
offset += fooSrc.length |
|
|
|
expect(foo.loc.end).toEqual({ line: 2, column: 5, offset }) |
|
|
|
|
|
|
|
expect(bar.loc.start).toEqual({ line: 2, column: 5, offset }) |
|
|
|
const barInner = (bar as InterpolationNode).content |
|
|
|
offset += 3 |
|
|
|
expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset }) |
|
|
|
offset += 3 |
|
|
|
expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset }) |
|
|
|
offset += 3 |
|
|
|
expect(bar.loc.end).toEqual({ line: 2, column: 14, offset }) |
|
|
|
|
|
|
|
expect(but.loc.start).toEqual({ line: 2, column: 14, offset }) |
|
|
|
offset += butSrc.length |
|
|
|
expect(but.loc.end).toEqual({ line: 2, column: 19, offset }) |
|
|
|
|
|
|
|
expect(baz.loc.start).toEqual({ line: 2, column: 19, offset }) |
|
|
|
const bazInner = (baz as InterpolationNode).content |
|
|
|
offset += 3 |
|
|
|
expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset }) |
|
|
|
offset += 3 |
|
|
|
expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset }) |
|
|
|
offset += 3 |
|
|
|
expect(baz.loc.end).toEqual({ line: 2, column: 28, offset }) |
|
|
|
}) |
|
|
|
|
|
|
|
// With standard HTML parsing, the following input would ignore the slash
|
|
|
|
// and treat "<" and "template" as attributes on the open tag of "Hello",
|
|
|
|
// causing `<template>` to fail to close, and `<script>` being parsed as its
|
|
|
|
// child. This is would never be intended in actual templates, but is a common
|
|
|
|
// intermediate state from user input when parsing for IDE support. We want
|
|
|
|
// the `<script>` to be at root-level to keep the SFC structure stable for
|
|
|
|
// Volar to do incremental computations.
|
|
|
|
test('tag termination handling for IDE', () => { |
|
|
|
const spy = vi.fn() |
|
|
|
const ast = baseParse( |
|
|
|
`<template><Hello\n</template><script>console.log(1)</script>`, |
|
|
|
{ |
|
|
|
onError: spy |
|
|
|
} |
|
|
|
) |
|
|
|
//
|
|
|
|
expect(ast.children.length).toBe(2) |
|
|
|
expect(ast.children[1]).toMatchObject({ |
|
|
|
type: NodeTypes.ELEMENT, |
|
|
|
tag: 'script' |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
test('arg should be undefined on shorthand dirs with no arg', () => { |
|
|
|
const ast = baseParse(`<template #></template>`) |
|
|
|
const el = ast.children[0] as ElementNode |
|
|
|
expect(el.props[0]).toMatchObject({ |
|
|
|
type: NodeTypes.DIRECTIVE, |
|
|
|
name: 'slot', |
|
|
|
exp: undefined, |
|
|
|
arg: undefined |
|
|
|
test('arg should be undefined on shorthand dirs with no arg', () => { |
|
|
|
const ast = baseParse(`<template #></template>`) |
|
|
|
const el = ast.children[0] as ElementNode |
|
|
|
expect(el.props[0]).toMatchObject({ |
|
|
|
type: NodeTypes.DIRECTIVE, |
|
|
|
name: 'slot', |
|
|
|
exp: undefined, |
|
|
|
arg: undefined |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
// edge case found in vue-macros where the input is TS or JSX
|
|
|
|
test('should reset inRCDATA state', () => { |
|
|
|
baseParse(`<Foo>`, { parseMode: 'sfc', onError() {} }) |
|
|
|
expect(() => baseParse(`{ foo }`)).not.toThrow() |
|
|
|
// edge case found in vue-macros where the input is TS or JSX
|
|
|
|
test('should reset inRCDATA state', () => { |
|
|
|
baseParse(`<Foo>`, { parseMode: 'sfc', onError() {} }) |
|
|
|
expect(() => baseParse(`{ foo }`)).not.toThrow() |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
describe('decodeEntities option', () => { |
|
|
|