Преглед изворни кода

test(compiler-vapor): v-model (#132)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
FireBushtree пре 2 година
родитељ
комит
ba29b4c89a

+ 236 - 0
packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap

@@ -0,0 +1,236 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiler: vModel transform > modifiers > .lazy 1`] = `
+"import { children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelText, () => _ctx.model, void 0, { lazy: true }]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > modifiers > .number 1`] = `
+"import { children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelText, () => _ctx.model, void 0, { number: true }]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > modifiers > .trim 1`] = `
+"import { children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelText, () => _ctx.model, void 0, { trim: true }]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support input (checkbox) 1`] = `
+"import { children as _children, vModelCheckbox as _vModelCheckbox, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input type=\\"checkbox\\">")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelCheckbox, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support input (dynamic type) 1`] = `
+"import { children as _children, vModelDynamic as _vModelDynamic, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelDynamic, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support input (radio) 1`] = `
+"import { children as _children, vModelRadio as _vModelRadio, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input type=\\"radio\\">")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelRadio, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support input (text) 1`] = `
+"import { children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input type=\\"text\\">")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelText, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support select 1`] = `
+"import { children as _children, vModelSelect as _vModelSelect, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<select></select>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelSelect, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support simple expression 1`] = `
+"import { children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelText, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support textarea 1`] = `
+"import { children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<textarea></textarea>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelText, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support w/ dynamic v-bind 1`] = `
+"import { children as _children, vModelDynamic as _vModelDynamic, withDirectives as _withDirectives, on as _on, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelDynamic, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  _renderEffect(() => _setDynamicProps(n1, _ctx.obj))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should support w/ dynamic v-bind 2`] = `
+"import { children as _children, vModelDynamic as _vModelDynamic, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelDynamic, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should work with input (checkbox) 1`] = `
+"import { children as _children, vModelCheckbox as _vModelCheckbox, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input type=\\"checkbox\\">")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelCheckbox, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should work with input (dynamic type) 1`] = `
+"import { children as _children, vModelDynamic as _vModelDynamic, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelDynamic, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should work with input (radio) 1`] = `
+"import { children as _children, vModelRadio as _vModelRadio, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input type=\\"radio\\">")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelRadio, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should work with input (text) 1`] = `
+"import { children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input type=\\"text\\">")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelText, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should work with select 1`] = `
+"import { children as _children, vModelSelect as _vModelSelect, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<select></select>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelSelect, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;
+
+exports[`compiler: vModel transform > should work with simple expression 1`] = `
+"import { children as _children, vModelText as _vModelText, withDirectives as _withDirectives, on as _on, template as _template } from 'vue/vapor';
+const t0 = _template("<input>")
+
+export function render(_ctx) {
+  const n0 = t0()
+  const n1 = _children(n0, 0)
+  _withDirectives(n1, [[_vModelText, () => _ctx.model]])
+  _on(n1, "update:modelValue", $event => (_ctx.model = $event))
+  return n0
+}"
+`;

+ 172 - 2
packages/compiler-vapor/__tests__/transforms/vModel.spec.ts

@@ -1,4 +1,174 @@
-// TODO: add tests for this transform
+import { makeCompile } from './_utils'
+import { transformElement, transformVModel } from '../../src'
+import { DOMErrorCodes } from '@vue/compiler-dom'
+
+const compileWithVModel = makeCompile({
+  nodeTransforms: [transformElement],
+  directiveTransforms: {
+    model: transformVModel,
+  },
+})
+
 describe('compiler: vModel transform', () => {
 describe('compiler: vModel transform', () => {
-  test.todo('basic')
+  test('should support simple expression', () => {
+    const { code, vaporHelpers } = compileWithVModel(
+      '<input v-model="model" />',
+    )
+
+    expect(vaporHelpers).toContain('vModelText')
+    expect(code).toMatchSnapshot()
+  })
+
+  test('should support input (text)', () => {
+    const { code, vaporHelpers } = compileWithVModel(
+      '<input type="text" v-model="model" />',
+    )
+    expect(vaporHelpers).toContain('vModelText')
+    expect(code).toMatchSnapshot()
+  })
+
+  test('should support input (radio)', () => {
+    const { code, vaporHelpers } = compileWithVModel(
+      '<input type="radio" v-model="model" />',
+    )
+    expect(vaporHelpers).toContain('vModelRadio')
+    expect(code).toMatchSnapshot()
+  })
+
+  test('should support input (checkbox)', () => {
+    const { code, vaporHelpers } = compileWithVModel(
+      '<input type="checkbox" v-model="model" />',
+    )
+    expect(vaporHelpers).toContain('vModelCheckbox')
+    expect(code).toMatchSnapshot()
+  })
+
+  test('should support select', () => {
+    const { code, vaporHelpers } = compileWithVModel(
+      '<select v-model="model" />',
+    )
+
+    expect(vaporHelpers).toContain('vModelSelect')
+    expect(code).toMatchSnapshot()
+  })
+
+  test('should support textarea', () => {
+    const { code, vaporHelpers } = compileWithVModel(
+      '<textarea v-model="model" />',
+    )
+
+    expect(vaporHelpers).toContain('vModelText')
+    expect(code).toMatchSnapshot()
+  })
+
+  test('should support input (dynamic type)', () => {
+    const { code, vaporHelpers } = compileWithVModel(
+      '<input :type="foo" v-model="model" />',
+    )
+    expect(vaporHelpers).toContain('vModelDynamic')
+    expect(code).toMatchSnapshot()
+  })
+
+  test('should support w/ dynamic v-bind', () => {
+    const root1 = compileWithVModel('<input v-bind="obj" v-model="model" />')
+    expect(root1.vaporHelpers).toContain('vModelDynamic')
+    expect(root1.code).toMatchSnapshot()
+
+    const root2 = compileWithVModel(
+      '<input v-bind:[key]="val" v-model="model" />',
+    )
+    expect(root2.vaporHelpers).toContain('vModelDynamic')
+    expect(root2.code).toMatchSnapshot()
+  })
+
+  describe('errors', () => {
+    test('invalid element', () => {
+      const onError = vi.fn()
+      compileWithVModel('<span v-model="model" />', { onError })
+
+      expect(onError).toHaveBeenCalledTimes(1)
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
+        }),
+      )
+    })
+
+    // TODO: component
+    test.fails('plain elements with argument', () => {
+      const onError = vi.fn()
+      compileWithVModel('<input v-model:value="model" />', { onError })
+
+      expect(onError).toHaveBeenCalledTimes(1)
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
+        }),
+      )
+    })
+
+    // TODO: component
+    test.fails('should allow usage on custom element', () => {
+      const onError = vi.fn()
+      const root = compileWithVModel('<my-input v-model="model" />', {
+        onError,
+        isCustomElement: tag => tag.startsWith('my-'),
+      })
+      expect(root.helpers).toContain('vModelText')
+      expect(onError).not.toHaveBeenCalled()
+    })
+
+    test('should raise error if used file input element', () => {
+      const onError = vi.fn()
+      compileWithVModel(`<input type="file" v-model="test"/>`, {
+        onError,
+      })
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
+        }),
+      )
+    })
+
+    test('should error on dynamic value binding alongside v-model', () => {
+      const onError = vi.fn()
+      compileWithVModel(`<input v-model="test" :value="test" />`, {
+        onError,
+      })
+      expect(onError).toHaveBeenCalledWith(
+        expect.objectContaining({
+          code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
+        }),
+      )
+    })
+
+    // #3596
+    test('should NOT error on static value binding alongside v-model', () => {
+      const onError = vi.fn()
+      compileWithVModel(`<input v-model="test" value="test" />`, {
+        onError,
+      })
+      expect(onError).not.toHaveBeenCalled()
+    })
+  })
+
+  describe('modifiers', () => {
+    test('.number', () => {
+      const { code } = compileWithVModel('<input v-model.number="model" />')
+
+      expect(code).toMatchSnapshot()
+    })
+
+    test('.trim', () => {
+      const { code } = compileWithVModel('<input v-model.trim="model" />')
+
+      expect(code).toMatchSnapshot()
+    })
+
+    test('.lazy', () => {
+      const { code } = compileWithVModel('<input v-model.lazy="model" />')
+
+      expect(code).toMatchSnapshot()
+    })
+  })
 })
 })

+ 1 - 0
packages/compiler-vapor/src/index.ts

@@ -19,3 +19,4 @@ export { transformVShow } from './transforms/vShow'
 export { transformVText } from './transforms/vText'
 export { transformVText } from './transforms/vText'
 export { transformVIf } from './transforms/vIf'
 export { transformVIf } from './transforms/vIf'
 export { transformVFor } from './transforms/vFor'
 export { transformVFor } from './transforms/vFor'
+export { transformVModel } from './transforms/vModel'