|
|
@@ -394,6 +394,121 @@ describe('compiler: element transform', () => {
|
|
|
expect(code).contains(`arrLabel: () =>`)
|
|
|
})
|
|
|
|
|
|
+ test('object literal v-bind props are expanded', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<Foo v-bind="{ foo: bar, baz: 1, id: 'x', formatter: v => v + 1 }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`foo: () => (_ctx.bar)`)
|
|
|
+ expect(code).contains(`baz: 1`)
|
|
|
+ expect(code).contains(`id: "x"`)
|
|
|
+ expect(code).contains(`formatter: () => (v => v + 1)`)
|
|
|
+ expect(code).not.contains(`$: [`)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('object literal v-bind preserves dynamic source merge order', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<Foo :[name]="value" v-bind="{ foo: bar }" />`,
|
|
|
+ )
|
|
|
+ const { code: beforeCode } = compileWithElementTransform(
|
|
|
+ `<Foo v-bind="{ foo: bar }" :[name]="value" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`$: [
|
|
|
+ () => ({ [_ctx.name]: _ctx.value }),
|
|
|
+ { foo: () => (_ctx.bar) }
|
|
|
+ ]`)
|
|
|
+ expect(beforeCode).toMatchSnapshot()
|
|
|
+ expect(beforeCode).contains(`foo: () => (_ctx.bar),
|
|
|
+ $: [
|
|
|
+ () => ({ [_ctx.name]: _ctx.value })
|
|
|
+ ]`)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('object literal v-bind joins existing static component props', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<Foo id="x" v-bind="{ foo: bar }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`id: "x"`)
|
|
|
+ expect(code).contains(`foo: () => (_ctx.bar)`)
|
|
|
+ expect(code).not.contains(`$: [`)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('unsupported object literal v-bind shapes stay as dynamic sources', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<Foo v-bind="{ [foo]: 1, ...obj }" />`,
|
|
|
+ )
|
|
|
+ const { code: protoCode } = compileWithElementTransform(
|
|
|
+ `<Foo v-bind="{ __proto__: foo }" />`,
|
|
|
+ )
|
|
|
+ const { code: reservedCode } = compileWithElementTransform(
|
|
|
+ `<Foo v-bind="{ key: foo }" />`,
|
|
|
+ )
|
|
|
+ const { code: duplicateCode } = compileWithElementTransform(
|
|
|
+ `<Foo v-bind="{ foo: a, foo: b }" />`,
|
|
|
+ )
|
|
|
+ const { code: methodCode } = compileWithElementTransform(
|
|
|
+ `<Foo v-bind="{ foo() {} }" />`,
|
|
|
+ )
|
|
|
+ const { code: conflictCode } = compileWithElementTransform(
|
|
|
+ `<Foo foo="x" v-bind="{ foo: bar }" />`,
|
|
|
+ )
|
|
|
+ const { code: staticBindConflictCode } = compileWithElementTransform(
|
|
|
+ `<Foo :foo="a" v-bind="{ foo: b }" />`,
|
|
|
+ )
|
|
|
+ const { code: camelizedConflictCode } = compileWithElementTransform(
|
|
|
+ `<Foo foo-bar="x" v-bind="{ fooBar: bar }" />`,
|
|
|
+ )
|
|
|
+ const { code: vnodeHookConflictCode } = compileWithElementTransform(
|
|
|
+ `<Foo @vue:mounted="a" v-bind="{ onVnodeMounted: b }" />`,
|
|
|
+ )
|
|
|
+ const { code: clickRightConflictCode } = compileWithElementTransform(
|
|
|
+ `<Foo @click.right="a" v-bind="{ onContextmenu: b }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`$: [`)
|
|
|
+ expect(code).contains(`() => ({ [_ctx.foo]: 1, ..._ctx.obj })`)
|
|
|
+ expect(protoCode).toMatchSnapshot()
|
|
|
+ expect(protoCode).contains(`$: [`)
|
|
|
+ expect(protoCode).contains(`() => ({ __proto__: _ctx.foo })`)
|
|
|
+ expect(reservedCode).toMatchSnapshot()
|
|
|
+ expect(reservedCode).contains(`$: [`)
|
|
|
+ expect(reservedCode).contains(`() => ({ key: _ctx.foo })`)
|
|
|
+ expect(duplicateCode).toMatchSnapshot()
|
|
|
+ expect(duplicateCode).contains(`$: [`)
|
|
|
+ expect(duplicateCode).contains(`() => ({ foo: _ctx.a, foo: _ctx.b })`)
|
|
|
+ expect(methodCode).toMatchSnapshot()
|
|
|
+ expect(methodCode).contains(`$: [`)
|
|
|
+ expect(methodCode).contains(`() => ({ foo() {} })`)
|
|
|
+ expect(conflictCode).toMatchSnapshot()
|
|
|
+ expect(conflictCode).contains(`foo: "x"`)
|
|
|
+ expect(conflictCode).contains(`$: [
|
|
|
+ () => ({ foo: _ctx.bar })
|
|
|
+ ]`)
|
|
|
+ expect(staticBindConflictCode).toMatchSnapshot()
|
|
|
+ expect(staticBindConflictCode).contains(`$: [
|
|
|
+ () => ({ foo: _ctx.b })
|
|
|
+ ]`)
|
|
|
+ expect(camelizedConflictCode).toMatchSnapshot()
|
|
|
+ expect(camelizedConflictCode).contains(`"foo-bar": "x"`)
|
|
|
+ expect(camelizedConflictCode).contains(`$: [
|
|
|
+ () => ({ fooBar: _ctx.bar })
|
|
|
+ ]`)
|
|
|
+ expect(vnodeHookConflictCode).toMatchSnapshot()
|
|
|
+ expect(vnodeHookConflictCode).contains(`$: [
|
|
|
+ () => ({ onVnodeMounted: _ctx.b })
|
|
|
+ ]`)
|
|
|
+ expect(clickRightConflictCode).toMatchSnapshot()
|
|
|
+ expect(clickRightConflictCode).contains(`$: [
|
|
|
+ () => ({ onContextmenu: _ctx.b })
|
|
|
+ ]`)
|
|
|
+ })
|
|
|
+
|
|
|
test('v-bind="obj"', () => {
|
|
|
const { code, ir } = compileWithElementTransform(`<Foo v-bind="obj" />`)
|
|
|
expect(code).toMatchSnapshot()
|
|
|
@@ -541,6 +656,80 @@ describe('compiler: element transform', () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+ test('object literal v-on handlers are expanded', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<Foo v-on="{ click: onClick, input: onInput, 'foo-bar': onFooBar }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`onClick: () => (_ctx.onClick)`)
|
|
|
+ expect(code).contains(`onInput: () => (_ctx.onInput)`)
|
|
|
+ expect(code).contains(`"onFoo-bar": () => (_ctx.onFooBar)`)
|
|
|
+ expect(code).not.contains(`_toHandlers`)
|
|
|
+ expect(code).not.contains(`$: [`)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('object literal v-on joins existing static component props', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<Foo id="x" v-on="{ click: onClick }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`id: "x"`)
|
|
|
+ expect(code).contains(`onClick: () => (_ctx.onClick)`)
|
|
|
+ expect(code).not.contains(`_toHandlers`)
|
|
|
+ expect(code).not.contains(`$: [`)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('object literal v-on conflicts with existing component handlers stay dynamic', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<Foo v-on="{ click: a }" @click="b" />`,
|
|
|
+ )
|
|
|
+ const { code: bindCode } = compileWithElementTransform(
|
|
|
+ `<Foo v-on="{ click: a }" v-bind="{ onClick: b }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`_toHandlers`)
|
|
|
+ expect(code).contains(`() => (_toHandlers({ click: _ctx.a }))`)
|
|
|
+ expect(code).contains(`{ onClick: () => _ctx.b }`)
|
|
|
+ expect(bindCode).toMatchSnapshot()
|
|
|
+ expect(bindCode).contains(`_toHandlers`)
|
|
|
+ expect(bindCode).contains(`() => (_toHandlers({ click: _ctx.a }))`)
|
|
|
+ expect(bindCode).contains(`{ onClick: () => (_ctx.b) }`)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('unsupported object literal v-on shapes stay as dynamic sources', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<Foo v-on="{ [event]: onClick, ...listeners }" />`,
|
|
|
+ )
|
|
|
+ const { code: protoCode } = compileWithElementTransform(
|
|
|
+ `<Foo v-on="{ __proto__: onClick }" />`,
|
|
|
+ )
|
|
|
+ const { code: duplicateCode } = compileWithElementTransform(
|
|
|
+ `<Foo v-on="{ click: onClick, Click: onClick2 }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`_toHandlers`)
|
|
|
+ expect(code).contains(`$: [`)
|
|
|
+ expect(code).contains(
|
|
|
+ `() => (_toHandlers({ [_ctx.event]: _ctx.onClick, ..._ctx.listeners }))`,
|
|
|
+ )
|
|
|
+ expect(protoCode).toMatchSnapshot()
|
|
|
+ expect(protoCode).contains(`_toHandlers`)
|
|
|
+ expect(protoCode).contains(`$: [`)
|
|
|
+ expect(protoCode).contains(
|
|
|
+ `() => (_toHandlers({ __proto__: _ctx.onClick }))`,
|
|
|
+ )
|
|
|
+ expect(duplicateCode).toMatchSnapshot()
|
|
|
+ expect(duplicateCode).contains(`_toHandlers`)
|
|
|
+ expect(duplicateCode).contains(`$: [`)
|
|
|
+ expect(duplicateCode).contains(
|
|
|
+ `() => (_toHandlers({ click: _ctx.onClick, Click: _ctx.onClick2 }))`,
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
test('v-on="obj" before static event keeps handler getters', () => {
|
|
|
const { code } = compileWithElementTransform(
|
|
|
`<Foo v-on="obj" @foo="bar" />`,
|
|
|
@@ -920,6 +1109,99 @@ describe('compiler: element transform', () => {
|
|
|
expect(code).contains('_setDynamicProps(n0, [_ctx.obj])')
|
|
|
})
|
|
|
|
|
|
+ test('object literal v-bind prop is lowered to setProp', () => {
|
|
|
+ const { code, ir } = compileWithElementTransform(
|
|
|
+ `<div v-bind="{ id: foo }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`_setProp(n0, "id", _ctx.foo)`)
|
|
|
+ expect(code).not.contains(`_setDynamicProps`)
|
|
|
+ expect(ir.block.effect).toMatchObject([
|
|
|
+ {
|
|
|
+ expressions: [{ content: 'foo' }],
|
|
|
+ operations: [
|
|
|
+ {
|
|
|
+ type: IRNodeTypes.SET_PROP,
|
|
|
+ element: 0,
|
|
|
+ prop: {
|
|
|
+ key: { content: 'id' },
|
|
|
+ values: [{ content: 'foo' }],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ })
|
|
|
+
|
|
|
+ test('object literal v-bind before dynamic key tracks the original source', () => {
|
|
|
+ const { code, ir } = compileWithElementTransform(
|
|
|
+ `<div v-bind="{ id: foo }" :[name]="bar" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(
|
|
|
+ `_setDynamicProps(n0, [{ id: _ctx.foo }, { [_ctx.name]: _ctx.bar }])`,
|
|
|
+ )
|
|
|
+ expect(ir.block.effect).toMatchObject([
|
|
|
+ {
|
|
|
+ expressions: [
|
|
|
+ { content: '{ id: foo }' },
|
|
|
+ { content: 'name' },
|
|
|
+ { content: 'bar' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ })
|
|
|
+
|
|
|
+ test('constant object literal v-bind props are lowered to template attrs', () => {
|
|
|
+ const { code, ir } = compileWithElementTransform(
|
|
|
+ `<div v-bind="{ id: 'foo', disabled: true }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(`_template("<div id=foo disabled>", 3)`)
|
|
|
+ expect(code).not.contains(`_renderEffect`)
|
|
|
+ expect(code).not.contains(`_setDynamicProps`)
|
|
|
+ expect(ir.block.effect).lengthOf(0)
|
|
|
+ })
|
|
|
+
|
|
|
+ test('order-sensitive object literal v-bind props stay dynamic', () => {
|
|
|
+ const { code } = compileWithElementTransform(
|
|
|
+ `<div id="foo" v-bind="{ id: bar }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(code).toMatchSnapshot()
|
|
|
+ expect(code).contains(
|
|
|
+ `_setDynamicProps(n0, [{ id: "foo" }, { id: _ctx.bar }])`,
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ test('unsafe native object literal v-bind props stay dynamic', () => {
|
|
|
+ const { code: eventCode } = compileWithElementTransform(
|
|
|
+ `<div v-bind="{ onClick: click }" />`,
|
|
|
+ )
|
|
|
+ const { code: prefixCode } = compileWithElementTransform(
|
|
|
+ `<input v-bind="{ '.value': value }" />`,
|
|
|
+ )
|
|
|
+ const { code: unsafeNameCode } = compileWithElementTransform(
|
|
|
+ `<div v-bind="{ 'foo bar': 'x' }" />`,
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(eventCode).toMatchSnapshot()
|
|
|
+ expect(eventCode).contains(
|
|
|
+ `_setDynamicProps(n0, [{ onClick: _ctx.click }])`,
|
|
|
+ )
|
|
|
+ expect(prefixCode).toMatchSnapshot()
|
|
|
+ expect(prefixCode).contains(
|
|
|
+ `_setDynamicProps(n0, [{ '.value': _ctx.value }])`,
|
|
|
+ )
|
|
|
+ expect(unsafeNameCode).toMatchSnapshot()
|
|
|
+ expect(unsafeNameCode).contains(
|
|
|
+ `_setDynamicProps(n0, [{ 'foo bar': 'x' }])`,
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
test('v-bind="obj" after static prop', () => {
|
|
|
const { code, ir } = compileWithElementTransform(
|
|
|
`<div id="foo" v-bind="obj" />`,
|