feat(compiler-core): create transform for v-model (#146)
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compiler: transform v-model compound expression (with prefixIdentifiers) 1`] = `
|
||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(\\"input\\", {
|
||||
modelValue: _ctx.model[_ctx.index],
|
||||
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model compound expression 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(\\"input\\", {
|
||||
modelValue: model[index],
|
||||
\\"onUpdate:modelValue\\": $event => (model[index] = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model simple exprssion (with prefixIdentifiers) 1`] = `
|
||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(\\"input\\", {
|
||||
modelValue: _ctx.model,
|
||||
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model simple exprssion 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(\\"input\\", {
|
||||
modelValue: model,
|
||||
\\"onUpdate:modelValue\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model with argument 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(\\"input\\", {
|
||||
value: model,
|
||||
\\"onUpdate:value\\": $event => (model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"value\\", \\"onUpdate:value\\"]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model with dynamic argument (with prefixIdentifiers) 1`] = `
|
||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(\\"input\\", {
|
||||
[_ctx.value]: _ctx.model,
|
||||
[\\"onUpdate:\\"+_ctx.value]: $event => (_ctx.model = $event)
|
||||
}, null, 16 /* FULL_PROPS */))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model with dynamic argument 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render() {
|
||||
with (this) {
|
||||
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createBlock(\\"input\\", {
|
||||
[value]: model,
|
||||
[\\"onUpdate:\\"+value]: $event => (model = $event)
|
||||
}, null, 16 /* FULL_PROPS */))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
355
packages/compiler-core/__tests__/transforms/vModel.spec.ts
Normal file
355
packages/compiler-core/__tests__/transforms/vModel.spec.ts
Normal file
@@ -0,0 +1,355 @@
|
||||
import {
|
||||
parse,
|
||||
transform,
|
||||
generate,
|
||||
ElementNode,
|
||||
ObjectExpression,
|
||||
CompilerOptions,
|
||||
CallExpression
|
||||
} from '../../src'
|
||||
import { ErrorCodes } from '../../src/errors'
|
||||
import { transformModel } from '../../src/transforms/vModel'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||
|
||||
function parseWithVModel(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformExpression, transformElement],
|
||||
directiveTransforms: {
|
||||
...options.directiveTransforms,
|
||||
model: transformModel
|
||||
},
|
||||
...options
|
||||
})
|
||||
|
||||
return ast
|
||||
}
|
||||
|
||||
describe('compiler: transform v-model', () => {
|
||||
test('simple exprssion', () => {
|
||||
const root = parseWithVModel('<input v-model="model" />')
|
||||
const node = root.children[0] as ElementNode
|
||||
const props = ((node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
|
||||
expect(props[0]).toMatchObject({
|
||||
key: {
|
||||
content: 'modelValue',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
content: 'model',
|
||||
isStatic: false
|
||||
}
|
||||
})
|
||||
|
||||
expect(props[1]).toMatchObject({
|
||||
key: {
|
||||
content: 'onUpdate:modelValue',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
children: [
|
||||
'$event => (',
|
||||
{
|
||||
content: 'model',
|
||||
isStatic: false
|
||||
},
|
||||
' = $event)'
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('simple exprssion (with prefixIdentifiers)', () => {
|
||||
const root = parseWithVModel('<input v-model="model" />', {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const node = root.children[0] as ElementNode
|
||||
const props = ((node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
|
||||
expect(props[0]).toMatchObject({
|
||||
key: {
|
||||
content: 'modelValue',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
content: '_ctx.model',
|
||||
isStatic: false
|
||||
}
|
||||
})
|
||||
|
||||
expect(props[1]).toMatchObject({
|
||||
key: {
|
||||
content: 'onUpdate:modelValue',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
children: [
|
||||
'$event => (',
|
||||
{
|
||||
content: '_ctx.model',
|
||||
isStatic: false
|
||||
},
|
||||
' = $event)'
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('compound expression', () => {
|
||||
const root = parseWithVModel('<input v-model="model[index]" />')
|
||||
const node = root.children[0] as ElementNode
|
||||
const props = ((node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
|
||||
expect(props[0]).toMatchObject({
|
||||
key: {
|
||||
content: 'modelValue',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
content: 'model[index]',
|
||||
isStatic: false
|
||||
}
|
||||
})
|
||||
|
||||
expect(props[1]).toMatchObject({
|
||||
key: {
|
||||
content: 'onUpdate:modelValue',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
children: [
|
||||
'$event => (',
|
||||
{
|
||||
content: 'model[index]',
|
||||
isStatic: false
|
||||
},
|
||||
' = $event)'
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('compound expression (with prefixIdentifiers)', () => {
|
||||
const root = parseWithVModel('<input v-model="model[index]" />', {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const node = root.children[0] as ElementNode
|
||||
const props = ((node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
|
||||
expect(props[0]).toMatchObject({
|
||||
key: {
|
||||
content: 'modelValue',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
children: [
|
||||
{
|
||||
content: '_ctx.model',
|
||||
isStatic: false
|
||||
},
|
||||
'[',
|
||||
{
|
||||
content: '_ctx.index',
|
||||
isStatic: false
|
||||
},
|
||||
']'
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(props[1]).toMatchObject({
|
||||
key: {
|
||||
content: 'onUpdate:modelValue',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
children: [
|
||||
'$event => (',
|
||||
{
|
||||
content: '_ctx.model',
|
||||
isStatic: false
|
||||
},
|
||||
'[',
|
||||
{
|
||||
content: '_ctx.index',
|
||||
isStatic: false
|
||||
},
|
||||
']',
|
||||
' = $event)'
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('with argument', () => {
|
||||
const root = parseWithVModel('<input v-model:value="model" />')
|
||||
const node = root.children[0] as ElementNode
|
||||
const props = ((node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
|
||||
expect(props[0]).toMatchObject({
|
||||
key: {
|
||||
content: 'value',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
content: 'model',
|
||||
isStatic: false
|
||||
}
|
||||
})
|
||||
|
||||
expect(props[1]).toMatchObject({
|
||||
key: {
|
||||
content: 'onUpdate:value',
|
||||
isStatic: true
|
||||
},
|
||||
value: {
|
||||
children: [
|
||||
'$event => (',
|
||||
{
|
||||
content: 'model',
|
||||
isStatic: false
|
||||
},
|
||||
' = $event)'
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('with dynamic argument', () => {
|
||||
const root = parseWithVModel('<input v-model:[value]="model" />')
|
||||
const node = root.children[0] as ElementNode
|
||||
const props = ((node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
|
||||
expect(props[0]).toMatchObject({
|
||||
key: {
|
||||
content: 'value',
|
||||
isStatic: false
|
||||
},
|
||||
value: {
|
||||
content: 'model',
|
||||
isStatic: false
|
||||
}
|
||||
})
|
||||
|
||||
expect(props[1]).toMatchObject({
|
||||
key: {
|
||||
children: [
|
||||
{
|
||||
content: 'onUpdate:',
|
||||
isStatic: true
|
||||
},
|
||||
'+',
|
||||
{
|
||||
content: 'value',
|
||||
isStatic: false
|
||||
}
|
||||
]
|
||||
},
|
||||
value: {
|
||||
children: [
|
||||
'$event => (',
|
||||
{
|
||||
content: 'model',
|
||||
isStatic: false
|
||||
},
|
||||
' = $event)'
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('with dynamic argument (with prefixIdentifiers)', () => {
|
||||
const root = parseWithVModel('<input v-model:[value]="model" />', {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const node = root.children[0] as ElementNode
|
||||
const props = ((node.codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
|
||||
expect(props[0]).toMatchObject({
|
||||
key: {
|
||||
content: '_ctx.value',
|
||||
isStatic: false
|
||||
},
|
||||
value: {
|
||||
content: '_ctx.model',
|
||||
isStatic: false
|
||||
}
|
||||
})
|
||||
|
||||
expect(props[1]).toMatchObject({
|
||||
key: {
|
||||
children: [
|
||||
{
|
||||
content: 'onUpdate:',
|
||||
isStatic: true
|
||||
},
|
||||
'+',
|
||||
{
|
||||
content: '_ctx.value',
|
||||
isStatic: false
|
||||
}
|
||||
]
|
||||
},
|
||||
value: {
|
||||
children: [
|
||||
'$event => (',
|
||||
{
|
||||
content: '_ctx.model',
|
||||
isStatic: false
|
||||
},
|
||||
' = $event)'
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('errors', () => {
|
||||
test('missing expression', () => {
|
||||
const onError = jest.fn()
|
||||
parseWithVModel('<span v-model />', { onError })
|
||||
|
||||
expect(onError).toHaveBeenCalledTimes(1)
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: ErrorCodes.X_V_MODEL_NO_EXPRESSION
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('empty expression', () => {
|
||||
const onError = jest.fn()
|
||||
parseWithVModel('<span v-model="" />', { onError })
|
||||
|
||||
expect(onError).toHaveBeenCalledTimes(1)
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Position } from '../src/ast'
|
||||
import { getInnerRange, advancePositionWithClone } from '../src/utils'
|
||||
import { Position, NodeTypes } from '../src/ast'
|
||||
import {
|
||||
getInnerRange,
|
||||
advancePositionWithClone,
|
||||
isEmptyExpression
|
||||
} from '../src/utils'
|
||||
|
||||
function p(line: number, column: number, offset: number): Position {
|
||||
return { column, line, offset }
|
||||
@@ -67,3 +71,38 @@ describe('getInnerRange', () => {
|
||||
expect(loc2.end.offset).toBe(7)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isEmptyExpression', () => {
|
||||
test('empty', () => {
|
||||
expect(
|
||||
isEmptyExpression({
|
||||
content: '',
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
isStatic: true,
|
||||
loc: null as any
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('spaces', () => {
|
||||
expect(
|
||||
isEmptyExpression({
|
||||
content: ' \t ',
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
isStatic: true,
|
||||
loc: null as any
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('identifier', () => {
|
||||
expect(
|
||||
isEmptyExpression({
|
||||
content: 'foo',
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
isStatic: true,
|
||||
loc: null as any
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user