2019-11-15 00:50:13 +08:00
|
|
|
import { parse } from '../src'
|
2019-12-23 08:44:21 +08:00
|
|
|
import { baseParse, baseCompile } from '@vue/compiler-core'
|
2020-02-10 22:34:13 +08:00
|
|
|
import { SourceMapConsumer } from 'source-map'
|
2019-11-15 00:50:13 +08:00
|
|
|
|
|
|
|
describe('compiler:sfc', () => {
|
2019-11-29 04:21:02 +08:00
|
|
|
describe('source map', () => {
|
|
|
|
test('style block', () => {
|
2020-02-10 22:34:13 +08:00
|
|
|
// Padding determines how many blank lines will there be before the style block
|
|
|
|
const padding = Math.round(Math.random() * 10)
|
|
|
|
const style = parse(
|
|
|
|
`${'\n'.repeat(padding)}<style>\n.color {\n color: red;\n }\n</style>\n`
|
|
|
|
).descriptor.styles[0]
|
|
|
|
|
2019-11-29 04:21:02 +08:00
|
|
|
expect(style.map).not.toBeUndefined()
|
2020-02-10 22:34:13 +08:00
|
|
|
|
|
|
|
const consumer = new SourceMapConsumer(style.map!)
|
|
|
|
consumer.eachMapping(mapping => {
|
|
|
|
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
|
|
|
})
|
2019-11-29 04:21:02 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
test('script block', () => {
|
2020-02-10 22:34:13 +08:00
|
|
|
// Padding determines how many blank lines will there be before the style block
|
|
|
|
const padding = Math.round(Math.random() * 10)
|
|
|
|
const script = parse(
|
|
|
|
`${'\n'.repeat(padding)}<script>\nconsole.log(1)\n }\n</script>\n`
|
|
|
|
).descriptor.script
|
|
|
|
|
2019-11-29 04:21:02 +08:00
|
|
|
expect(script!.map).not.toBeUndefined()
|
2020-02-10 22:34:13 +08:00
|
|
|
|
|
|
|
const consumer = new SourceMapConsumer(script!.map!)
|
|
|
|
consumer.eachMapping(mapping => {
|
|
|
|
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
|
|
|
})
|
2019-11-29 04:21:02 +08:00
|
|
|
})
|
2020-08-15 05:47:28 +08:00
|
|
|
|
|
|
|
test('custom block', () => {
|
|
|
|
const padding = Math.round(Math.random() * 10)
|
|
|
|
const custom = parse(
|
|
|
|
`${'\n'.repeat(padding)}<i18n>\n{\n "greeting": "hello"\n}\n</i18n>\n`
|
|
|
|
).descriptor.customBlocks[0]
|
|
|
|
|
|
|
|
expect(custom!.map).not.toBeUndefined()
|
|
|
|
|
|
|
|
const consumer = new SourceMapConsumer(custom!.map!)
|
|
|
|
consumer.eachMapping(mapping => {
|
|
|
|
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
|
|
|
})
|
|
|
|
})
|
2019-11-29 04:21:02 +08:00
|
|
|
})
|
|
|
|
|
2019-12-02 23:43:30 +08:00
|
|
|
test('pad content', () => {
|
|
|
|
const content = `
|
|
|
|
<template>
|
|
|
|
<div></div>
|
|
|
|
</template>
|
|
|
|
<script>
|
|
|
|
export default {}
|
|
|
|
</script>
|
|
|
|
<style>
|
|
|
|
h1 { color: red }
|
2020-08-15 05:47:28 +08:00
|
|
|
</style>
|
|
|
|
<i18n>
|
|
|
|
{ "greeting": "hello" }
|
|
|
|
</i18n>
|
|
|
|
`
|
2019-12-23 08:44:21 +08:00
|
|
|
const padFalse = parse(content.trim(), { pad: false }).descriptor
|
2019-12-02 23:43:30 +08:00
|
|
|
expect(padFalse.template!.content).toBe('\n<div></div>\n')
|
|
|
|
expect(padFalse.script!.content).toBe('\nexport default {}\n')
|
|
|
|
expect(padFalse.styles[0].content).toBe('\nh1 { color: red }\n')
|
2020-08-15 05:47:28 +08:00
|
|
|
expect(padFalse.customBlocks[0].content).toBe('\n{ "greeting": "hello" }\n')
|
2019-12-02 23:43:30 +08:00
|
|
|
|
2019-12-23 08:44:21 +08:00
|
|
|
const padTrue = parse(content.trim(), { pad: true }).descriptor
|
2019-12-02 23:43:30 +08:00
|
|
|
expect(padTrue.script!.content).toBe(
|
|
|
|
Array(3 + 1).join('//\n') + '\nexport default {}\n'
|
|
|
|
)
|
|
|
|
expect(padTrue.styles[0].content).toBe(
|
|
|
|
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
|
|
|
|
)
|
2020-08-15 05:47:28 +08:00
|
|
|
expect(padTrue.customBlocks[0].content).toBe(
|
|
|
|
Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n'
|
|
|
|
)
|
2019-12-02 23:43:30 +08:00
|
|
|
|
2019-12-23 08:44:21 +08:00
|
|
|
const padLine = parse(content.trim(), { pad: 'line' }).descriptor
|
2019-12-02 23:43:30 +08:00
|
|
|
expect(padLine.script!.content).toBe(
|
|
|
|
Array(3 + 1).join('//\n') + '\nexport default {}\n'
|
|
|
|
)
|
|
|
|
expect(padLine.styles[0].content).toBe(
|
|
|
|
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
|
|
|
|
)
|
2020-08-15 05:47:28 +08:00
|
|
|
expect(padLine.customBlocks[0].content).toBe(
|
|
|
|
Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n'
|
|
|
|
)
|
2019-12-02 23:43:30 +08:00
|
|
|
|
2019-12-23 08:44:21 +08:00
|
|
|
const padSpace = parse(content.trim(), { pad: 'space' }).descriptor
|
2019-12-02 23:43:30 +08:00
|
|
|
expect(padSpace.script!.content).toBe(
|
|
|
|
`<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
|
|
|
|
'\nexport default {}\n'
|
|
|
|
)
|
|
|
|
expect(padSpace.styles[0].content).toBe(
|
|
|
|
`<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>`.replace(
|
|
|
|
/./g,
|
|
|
|
' '
|
|
|
|
) + '\nh1 { color: red }\n'
|
|
|
|
)
|
2020-08-15 05:47:28 +08:00
|
|
|
expect(padSpace.customBlocks[0].content).toBe(
|
|
|
|
`<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>\nh1 { color: red }\n</style>\n<i18n>`.replace(
|
|
|
|
/./g,
|
|
|
|
' '
|
|
|
|
) + '\n{ "greeting": "hello" }\n'
|
|
|
|
)
|
2019-12-02 23:43:30 +08:00
|
|
|
})
|
|
|
|
|
2021-07-20 06:54:35 +08:00
|
|
|
test('should parse correct range for root level self closing tag', () => {
|
|
|
|
const content = `\n <div/>\n`
|
|
|
|
const { descriptor } = parse(`<template>${content}</template>`)
|
|
|
|
expect(descriptor.template).toBeTruthy()
|
|
|
|
expect(descriptor.template!.content).toBe(content)
|
|
|
|
expect(descriptor.template!.loc).toMatchObject({
|
|
|
|
start: { line: 1, column: 11, offset: 10 },
|
|
|
|
end: {
|
|
|
|
line: 3,
|
|
|
|
column: 1,
|
|
|
|
offset: 10 + content.length
|
|
|
|
},
|
|
|
|
source: content
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-07-20 05:05:37 +08:00
|
|
|
test('should parse correct range for blocks with no content (self closing)', () => {
|
2020-12-05 07:23:01 +08:00
|
|
|
const { descriptor } = parse(`<template/>`)
|
|
|
|
expect(descriptor.template).toBeTruthy()
|
|
|
|
expect(descriptor.template!.content).toBeFalsy()
|
2021-07-20 05:05:37 +08:00
|
|
|
expect(descriptor.template!.loc).toMatchObject({
|
|
|
|
start: { line: 1, column: 1, offset: 0 },
|
|
|
|
end: { line: 1, column: 1, offset: 0 },
|
|
|
|
source: ''
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
test('should parse correct range for blocks with no content (explicit)', () => {
|
|
|
|
const { descriptor } = parse(`<template></template>`)
|
|
|
|
expect(descriptor.template).toBeTruthy()
|
|
|
|
expect(descriptor.template!.content).toBeFalsy()
|
|
|
|
expect(descriptor.template!.loc).toMatchObject({
|
|
|
|
start: { line: 1, column: 11, offset: 10 },
|
|
|
|
end: { line: 1, column: 11, offset: 10 },
|
|
|
|
source: ''
|
|
|
|
})
|
2020-12-05 07:23:01 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
test('should ignore other nodes with no content', () => {
|
2019-12-23 08:44:21 +08:00
|
|
|
expect(parse(`<script/>`).descriptor.script).toBe(null)
|
2021-07-20 05:24:40 +08:00
|
|
|
expect(parse(`<script> \n\t </script>`).descriptor.script).toBe(null)
|
2019-12-23 08:44:21 +08:00
|
|
|
expect(parse(`<style/>`).descriptor.styles.length).toBe(0)
|
2021-07-20 05:24:40 +08:00
|
|
|
expect(parse(`<style> \n\t </style>`).descriptor.styles.length).toBe(0)
|
2019-12-23 08:44:21 +08:00
|
|
|
expect(parse(`<custom/>`).descriptor.customBlocks.length).toBe(0)
|
2021-07-20 05:24:40 +08:00
|
|
|
expect(
|
|
|
|
parse(`<custom> \n\t </custom>`).descriptor.customBlocks.length
|
|
|
|
).toBe(0)
|
2019-11-19 22:10:59 +08:00
|
|
|
})
|
|
|
|
|
2020-02-04 23:03:32 +08:00
|
|
|
test('handle empty nodes with src attribute', () => {
|
|
|
|
const { descriptor } = parse(`<script src="com"/>`)
|
|
|
|
expect(descriptor.script).toBeTruthy()
|
|
|
|
expect(descriptor.script!.content).toBeFalsy()
|
|
|
|
expect(descriptor.script!.attrs['src']).toBe('com')
|
|
|
|
})
|
|
|
|
|
2021-07-20 07:02:46 +08:00
|
|
|
test('ignoreEmpty: false', () => {
|
|
|
|
const { descriptor } = parse(
|
|
|
|
`<script></script>\n<script setup>\n</script>`,
|
|
|
|
{
|
|
|
|
ignoreEmpty: false
|
|
|
|
}
|
|
|
|
)
|
|
|
|
expect(descriptor.script).toBeTruthy()
|
|
|
|
expect(descriptor.script!.loc).toMatchObject({
|
|
|
|
source: '',
|
|
|
|
start: { line: 1, column: 9, offset: 8 },
|
|
|
|
end: { line: 1, column: 9, offset: 8 }
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(descriptor.scriptSetup).toBeTruthy()
|
|
|
|
expect(descriptor.scriptSetup!.loc).toMatchObject({
|
|
|
|
source: '\n',
|
|
|
|
start: { line: 2, column: 15, offset: 32 },
|
|
|
|
end: { line: 3, column: 1, offset: 33 }
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2019-12-23 08:44:21 +08:00
|
|
|
test('nested templates', () => {
|
|
|
|
const content = `
|
|
|
|
<template v-if="ok">ok</template>
|
|
|
|
<div><div></div></div>
|
|
|
|
`
|
2019-12-23 10:09:39 +08:00
|
|
|
const { descriptor } = parse(`<template>${content}</template>`)
|
|
|
|
expect(descriptor.template!.content).toBe(content)
|
2019-12-23 08:44:21 +08:00
|
|
|
})
|
|
|
|
|
2021-02-04 02:11:45 +08:00
|
|
|
test('treat empty lang attribute as the html', () => {
|
|
|
|
const content = `<div><template v-if="ok">ok</template></div>`
|
|
|
|
const { descriptor, errors } = parse(
|
|
|
|
`<template lang="">${content}</template>`
|
|
|
|
)
|
|
|
|
expect(descriptor.template!.content).toBe(content)
|
|
|
|
expect(errors.length).toBe(0)
|
|
|
|
})
|
|
|
|
|
2020-05-07 23:08:17 +08:00
|
|
|
// #1120
|
|
|
|
test('alternative template lang should be treated as plain text', () => {
|
|
|
|
const content = `p(v-if="1 < 2") test`
|
|
|
|
const { descriptor, errors } = parse(
|
|
|
|
`<template lang="pug">` + content + `</template>`
|
|
|
|
)
|
|
|
|
expect(errors.length).toBe(0)
|
|
|
|
expect(descriptor.template!.content).toBe(content)
|
|
|
|
})
|
|
|
|
|
2020-12-01 03:45:26 +08:00
|
|
|
//#2566
|
|
|
|
test('div lang should not be treated as plain text', () => {
|
|
|
|
const { errors } = parse(`
|
|
|
|
<template lang="pug">
|
|
|
|
<div lang="">
|
|
|
|
<div></div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
`)
|
|
|
|
expect(errors.length).toBe(0)
|
|
|
|
})
|
|
|
|
|
2021-03-06 01:12:49 +08:00
|
|
|
test('slotted detection', async () => {
|
|
|
|
expect(parse(`<template>hi</template>`).descriptor.slotted).toBe(false)
|
|
|
|
expect(
|
|
|
|
parse(`<template>hi</template><style>h1{color:red;}</style>`).descriptor
|
|
|
|
.slotted
|
|
|
|
).toBe(false)
|
|
|
|
expect(
|
2021-03-06 06:46:37 +08:00
|
|
|
parse(
|
|
|
|
`<template>hi</template><style scoped>:slotted(h1){color:red;}</style>`
|
|
|
|
).descriptor.slotted
|
2021-03-06 01:12:49 +08:00
|
|
|
).toBe(true)
|
|
|
|
expect(
|
2021-03-06 06:46:37 +08:00
|
|
|
parse(
|
|
|
|
`<template>hi</template><style scoped>::v-slotted(h1){color:red;}</style>`
|
|
|
|
).descriptor.slotted
|
2021-03-06 01:12:49 +08:00
|
|
|
).toBe(true)
|
|
|
|
})
|
|
|
|
|
2019-12-23 08:44:21 +08:00
|
|
|
test('error tolerance', () => {
|
|
|
|
const { errors } = parse(`<template>`)
|
|
|
|
expect(errors.length).toBe(1)
|
|
|
|
})
|
|
|
|
|
|
|
|
test('should parse as DOM by default', () => {
|
|
|
|
const { errors } = parse(`<template><input></template>`)
|
|
|
|
expect(errors.length).toBe(0)
|
|
|
|
})
|
|
|
|
|
|
|
|
test('custom compiler', () => {
|
|
|
|
const { errors } = parse(`<template><input></template>`, {
|
|
|
|
compiler: {
|
|
|
|
parse: baseParse,
|
|
|
|
compile: baseCompile
|
|
|
|
}
|
|
|
|
})
|
|
|
|
expect(errors.length).toBe(1)
|
|
|
|
})
|
|
|
|
|
2019-12-23 10:09:39 +08:00
|
|
|
test('treat custom blocks as raw text', () => {
|
|
|
|
const { errors, descriptor } = parse(`<foo> <-& </foo>`)
|
|
|
|
expect(errors.length).toBe(0)
|
|
|
|
expect(descriptor.customBlocks[0].content).toBe(` <-& `)
|
|
|
|
})
|
|
|
|
|
2019-12-23 08:44:21 +08:00
|
|
|
describe('warnings', () => {
|
2020-07-16 04:00:53 +08:00
|
|
|
function assertWarning(errors: Error[], msg: string) {
|
|
|
|
expect(errors.some(e => e.message.match(msg))).toBe(true)
|
|
|
|
}
|
|
|
|
|
2019-11-15 00:50:13 +08:00
|
|
|
test('should only allow single template element', () => {
|
2020-07-16 04:00:53 +08:00
|
|
|
assertWarning(
|
|
|
|
parse(`<template><div/></template><template><div/></template>`).errors,
|
2020-07-04 03:08:41 +08:00
|
|
|
`Single file component can contain only one <template> element`
|
2020-07-16 04:00:53 +08:00
|
|
|
)
|
2019-11-15 00:50:13 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
test('should only allow single script element', () => {
|
2020-07-16 04:00:53 +08:00
|
|
|
assertWarning(
|
|
|
|
parse(`<script>console.log(1)</script><script>console.log(1)</script>`)
|
|
|
|
.errors,
|
2020-07-04 03:08:41 +08:00
|
|
|
`Single file component can contain only one <script> element`
|
2020-07-16 04:00:53 +08:00
|
|
|
)
|
2019-11-15 00:50:13 +08:00
|
|
|
})
|
2020-07-04 03:08:41 +08:00
|
|
|
|
|
|
|
test('should only allow single script setup element', () => {
|
2020-07-16 04:00:53 +08:00
|
|
|
assertWarning(
|
|
|
|
parse(
|
|
|
|
`<script setup>console.log(1)</script><script setup>console.log(1)</script>`
|
|
|
|
).errors,
|
2020-07-04 03:08:41 +08:00
|
|
|
`Single file component can contain only one <script setup> element`
|
2020-07-16 04:00:53 +08:00
|
|
|
)
|
2020-07-04 03:08:41 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
test('should not warn script & script setup', () => {
|
|
|
|
expect(
|
2020-07-16 04:00:53 +08:00
|
|
|
parse(
|
|
|
|
`<script setup>console.log(1)</script><script>console.log(1)</script>`
|
|
|
|
).errors.length
|
|
|
|
).toBe(0)
|
2020-07-04 03:08:41 +08:00
|
|
|
})
|
2019-11-15 00:50:13 +08:00
|
|
|
})
|
|
|
|
})
|