import { parse } from '../src'
import {
ElementTypes,
NodeTypes,
baseCompile,
createRoot,
} from '@vue/compiler-core'
import { SourceMapConsumer } from 'source-map-js'
describe('compiler:sfc', () => {
describe('source map', () => {
test('style block', () => {
// Padding determines how many blank lines will there be before the style block
const padding = Math.round(Math.random() * 10)
const src =
`${'\n'.repeat(padding)}` +
`
`
const {
descriptor: { styles },
} = parse(src)
expect(styles[0].map).not.toBeUndefined()
const consumer = new SourceMapConsumer(styles[0].map!)
const lineOffset =
src.slice(0, src.indexOf(`
{ "greeting": "hello" }
`
const padFalse = parse(content.trim(), { pad: false }).descriptor
expect(padFalse.template!.content).toBe('\n
\n')
expect(padFalse.script!.content).toBe('\nexport default {}\n')
expect(padFalse.styles[0].content).toBe('\nh1 { color: red }\n')
expect(padFalse.customBlocks[0].content).toBe('\n{ "greeting": "hello" }\n')
const padTrue = parse(content.trim(), { pad: true }).descriptor
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',
)
expect(padTrue.customBlocks[0].content).toBe(
Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n',
)
const padLine = parse(content.trim(), { pad: 'line' }).descriptor
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',
)
expect(padLine.customBlocks[0].content).toBe(
Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n',
)
const padSpace = parse(content.trim(), { pad: 'space' }).descriptor
expect(padSpace.script!.content).toBe(
`\n\n\n\n\n`.replace(
/./g,
' ',
) + '\n{ "greeting": "hello" }\n',
)
})
test('should parse correct range for root level self closing tag', () => {
const content = `\n \n`
const { descriptor } = parse(`${content}`)
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,
},
})
})
test('should parse correct range for blocks with no content (self closing)', () => {
const { descriptor } = parse(``)
expect(descriptor.template).toBeTruthy()
expect(descriptor.template!.content).toBeFalsy()
expect(descriptor.template!.loc).toMatchObject({
start: { line: 1, column: 12, offset: 11 },
end: { line: 1, column: 12, offset: 11 },
})
})
test('should parse correct range for blocks with no content (explicit)', () => {
const { descriptor } = parse(``)
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 },
})
})
test('should ignore other nodes with no content', () => {
expect(parse(``).descriptor.script).toBe(null)
expect(parse(``).descriptor.script).toBe(null)
expect(parse(``).descriptor.styles.length).toBe(0)
expect(parse(``).descriptor.styles.length).toBe(0)
expect(parse(``).descriptor.customBlocks.length).toBe(0)
expect(
parse(` \n\t `).descriptor.customBlocks.length,
).toBe(0)
})
test('handle empty nodes with src attribute', () => {
const { descriptor } = parse(``)
expect(descriptor.script).toBeTruthy()
expect(descriptor.script!.content).toBeFalsy()
expect(descriptor.script!.attrs['src']).toBe('com')
})
test('should not expose ast on template node if has src import', () => {
const { descriptor } = parse(``)
expect(descriptor.template!.ast).toBeUndefined()
})
test('ignoreEmpty: false', () => {
const { descriptor } = parse(
`\n`,
{
ignoreEmpty: false,
},
)
expect(descriptor.script).toBeTruthy()
expect(descriptor.script!.loc).toMatchObject({
start: { line: 1, column: 9, offset: 8 },
end: { line: 1, column: 9, offset: 8 },
})
expect(descriptor.scriptSetup).toBeTruthy()
expect(descriptor.scriptSetup!.loc).toMatchObject({
start: { line: 2, column: 15, offset: 32 },
end: { line: 3, column: 1, offset: 33 },
})
})
test('nested templates', () => {
const content = `
ok
`
const { descriptor } = parse(`${content}`)
expect(descriptor.template!.content).toBe(content)
})
test('treat empty lang attribute as the html', () => {
const content = `ok
`
const { descriptor, errors } = parse(
`${content}`,
)
expect(descriptor.template!.content).toBe(content)
expect(errors.length).toBe(0)
})
// #1120
test('template with preprocessor lang should be treated as plain text', () => {
const content = `p(v-if="1 < 2") test `
const { descriptor, errors } = parse(
`` + content + ``,
)
expect(errors.length).toBe(0)
expect(descriptor.template!.content).toBe(content)
// should not attempt to parse the content
expect(descriptor.template!.ast!.children.length).toBe(1)
})
//#2566
test('div lang should not be treated as plain text', () => {
const { errors } = parse(`
`)
expect(errors.length).toBe(0)
})
test('slotted detection', async () => {
expect(parse(`hi`).descriptor.slotted).toBe(false)
expect(
parse(`hi`).descriptor
.slotted,
).toBe(false)
expect(
parse(
`hi`,
).descriptor.slotted,
).toBe(true)
expect(
parse(
`hi`,
).descriptor.slotted,
).toBe(true)
})
test('error tolerance', () => {
const { errors } = parse(``)
expect(errors.length).toBe(1)
})
test('should parse as DOM by default', () => {
const { errors } = parse(``)
expect(errors.length).toBe(0)
})
test('custom compiler', () => {
const { errors } = parse(``, {
compiler: {
parse: (_, options) => {
options.onError!(new Error('foo') as any)
return createRoot([])
},
compile: baseCompile,
},
})
expect(errors.length).toBe(2)
// error thrown by the custom parse
expect(errors[0].message).toBe('foo')
// error thrown based on the returned root
expect(errors[1].message).toMatch('At least one')
})
test('treat custom blocks as raw text', () => {
const { errors, descriptor } = parse(
` <-& `,
)
expect(errors.length).toBe(0)
expect(descriptor.customBlocks[0].content).toBe(` <-& `)
})
test('should accept parser options', () => {
const { errors, descriptor } = parse(``, {
templateParseOptions: {
isCustomElement: t => t === 'hello',
},
})
expect(errors.length).toBe(0)
expect(descriptor.template!.ast!.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'hello',
tagType: ElementTypes.ELEMENT,
})
// test cache invalidation on different options
const { descriptor: d2 } = parse(``, {
templateParseOptions: {
isCustomElement: t => t !== 'hello',
},
})
expect(d2.template!.ast!.children[0]).toMatchObject({
type: NodeTypes.ELEMENT,
tag: 'hello',
tagType: ElementTypes.COMPONENT,
})
})
describe('vapor mode', () => {
test('on empty script', () => {
const { descriptor } = parse(``)
expect(descriptor.vapor).toBe(true)
})
test('on template', () => {
const { descriptor } = parse(``)
expect(descriptor.vapor).toBe(true)
})
})
describe('warnings', () => {
function assertWarning(errors: Error[], msg: string) {
expect(errors.some(e => e.message.match(msg))).toBe(true)
}
test('should only allow single template element', () => {
assertWarning(
parse(``).errors,
`Single file component can contain only one element`,
)
})
test('should only allow single script element', () => {
assertWarning(
parse(``)
.errors,
`Single file component can contain only one `,
).errors,
`Single file component can contain only one `,
).errors.length,
).toBe(0)
})
// # 6676
test('should throw error if no or