Back to Repositories

Validating defineModel Component Compilation in dcloudio/uni-app

This test suite validates the defineModel functionality in Vue.js component scripts, focusing on various model binding scenarios and type definitions. It ensures proper compilation and binding behavior for component props and emits using the defineModel syntax.

Test Coverage Overview

The test suite provides comprehensive coverage of defineModel implementations, including:
  • Basic usage scenarios with required props and custom model names
  • Integration with defineProps and defineEmits
  • Array props handling
  • Local flag implementations
  • TypeScript type definitions and validations
  • Production mode optimizations

Implementation Analysis

The testing approach systematically verifies the compilation output and binding types for various defineModel configurations. It employs Jest’s expect assertions to validate generated code patterns, prop definitions, emit declarations, and type inference mechanisms.

The tests specifically focus on the transformation of defineModel declarations into corresponding props and emits configurations.

Technical Details

Testing tools and setup include:
  • Jest as the primary testing framework
  • Vue compiler core utilities for SFC script compilation
  • Custom assertion utilities (assertCode)
  • TypeScript integration for type checking
  • Production mode compilation testing

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through:
  • Isolated test cases for specific functionality
  • Comprehensive type checking scenarios
  • Edge case coverage for various prop configurations
  • Clear test organization and naming
  • Validation of both development and production outputs

dcloudio/uni-app

packages/uni-app-uts/__tests__/android/sfc/compileScript/defineModel.spec.ts

            
import { BindingTypes } from '@vue/compiler-core'
import { assertCode, compileSFCScript as compile } from '../utils'

describe('defineModel()', () => {
  test('basic usage', () => {
    const { content, bindings } = compile(
      `
      <script setup>
      const modelValue = defineModel({ required: true })
      const c = defineModel('count')
      const toString = defineModel('toString', { type: Function })
      </script>
      `,
      { defineModel: true }
    )
    assertCode(content)
    expect(content).toMatch('props: {')
    expect(content).toMatch('"modelValue": { required: true },')
    expect(content).toMatch('"count": {},')
    expect(content).toMatch('"toString": { type: Function },')
    expect(content).toMatch(
      'emits: ["update:modelValue", "update:count", "update:toString"],'
    )
    expect(content).toMatch(
      `const modelValue = useModel<any>(__ins.props, "modelValue")`
    )
    expect(content).toMatch(`const c = useModel<any>(__ins.props, "count")`)
    // expect(content).toMatch(`return { modelValue, c, toString }`)
    expect(content).not.toMatch('defineModel')

    expect(bindings).toStrictEqual({
      modelValue: BindingTypes.SETUP_REF,
      count: BindingTypes.PROPS,
      c: BindingTypes.SETUP_REF,
      toString: BindingTypes.SETUP_REF,
    })
  })

  test('w/ defineProps and defineEmits', () => {
    const { content, bindings } = compile(
      `
      <script setup>
      defineProps({ foo: String })
      defineEmits(['change'])
      const count = defineModel({ default: 0 })
      </script>
    `,
      { defineModel: true }
    )
    assertCode(content)
    expect(content).toMatch(`props: /*#__PURE__*/mergeModels({ foo: String }`)
    expect(content).toMatch(`"modelValue": { default: 0 }`)
    expect(content).toMatch(
      `const count = useModel<any>(__ins.props, "modelValue")`
    )
    expect(content).not.toMatch('defineModel')
    expect(bindings).toStrictEqual({
      count: BindingTypes.SETUP_REF,
      foo: BindingTypes.PROPS,
      modelValue: BindingTypes.PROPS,
    })
  })

  test('w/ array props', () => {
    const { content, bindings } = compile(
      `
      <script setup>
      defineProps(['foo', 'bar'])
      const count = defineModel('count')
      </script>
    `,
      { defineModel: true }
    )
    assertCode(content)
    expect(content).toMatch(`props: /*#__PURE__*/mergeModels(['foo', 'bar'], {
    "count": {},
  })`)
    expect(content).toMatch(`const count = useModel<any>(__ins.props, "count")`)
    expect(content).not.toMatch('defineModel')
    expect(bindings).toStrictEqual({
      foo: BindingTypes.PROPS,
      bar: BindingTypes.PROPS,
      count: BindingTypes.SETUP_REF,
    })
  })

  test('w/ local flag', () => {
    const { content } = compile(
      `<script setup>
      const foo = defineModel({ local: true, default: 1 })
      const bar = defineModel('bar', { [key]: true })
      // const baz = defineModel('baz', { ...x })
      // const qux = defineModel('qux', x)

      // const foo2 = defineModel('foo2', { local: true, ...x })

      const local = true
      const hoist = defineModel('hoist', { local })
      </script>`,
      { defineModel: true }
    )
    assertCode(content)
    expect(content).toMatch(
      `useModel<any>(__ins.props, "modelValue", { local: true })`
    )
    expect(content).toMatch(
      `useModel<any>(__ins.props, "bar", { [key]: true })`
    )
    // expect(content).toMatch(`useModel(__ins.props, "baz", { ...x })`)
    // expect(content).toMatch(`useModel(__ins.props, "qux", x)`)
    // expect(content).toMatch(`useModel(__ins.props, "foo2", { local: true })`)
    expect(content).toMatch(`useModel<any>(__ins.props, "hoist", { local })`)
  })

  test('w/ types, basic usage', () => {
    const { content, bindings } = compile(
      `
      <script setup lang="ts">
      const modelValue = defineModel<boolean | string>()
      const count = defineModel<number>('count')
      const disabled = defineModel<number>('disabled', { required: false })
      const any = defineModel<any | boolean>('any')
      const arr = defineModel<string[]>('arr')
      const arr1 = defineModel('arr1', { type: Array as PropType<string[]> })
      </script>
      `,
      { defineModel: true }
    )
    assertCode(content)
    expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
    expect(content).toMatch('"count": { type: Number }')
    expect(content).toMatch(
      '"disabled": { type: Number, ...{ required: false } }'
    )
    expect(content).toMatch('"any": { type: [Object, Boolean] }')
    expect(content).toMatch('"arr": { type: Array as PropType<string[]> }')
    expect(content).toMatch('"arr1": { type: Array as PropType<string[]> }')
    expect(content).toMatch(
      'emits: ["update:modelValue", "update:count", "update:disabled", "update:any", "update:arr", "update:arr1"]'
    )

    expect(content).toMatch(
      `const modelValue = useModel<any>(__ins.props, "modelValue")`
    )
    expect(content).toMatch(
      `const count = useModel<number>(__ins.props, "count")`
    )
    expect(content).toMatch(
      `const disabled = useModel<number>(__ins.props, "disabled")`
    )
    expect(content).toMatch(`const any = useModel<any>(__ins.props, "any")`)
    expect(content).toMatch(
      `const arr = useModel<string[]>(__ins.props, "arr")`
    )
    expect(content).toMatch(
      `const arr1 = useModel<string[]>(__ins.props, "arr1")`
    )

    expect(bindings).toStrictEqual({
      modelValue: BindingTypes.SETUP_REF,
      count: BindingTypes.SETUP_REF,
      disabled: BindingTypes.SETUP_REF,
      any: BindingTypes.SETUP_REF,
      arr: BindingTypes.SETUP_REF,
      arr1: BindingTypes.SETUP_REF,
    })
  })

  test('w/ types, production mode', () => {
    const { content, bindings } = compile(
      `
      <script setup lang="ts">
      const modelValue = defineModel<boolean>()
      const fn = defineModel<() => void>('fn')
      const fnWithDefault = defineModel<() => void>('fnWithDefault', { default: () => null })
      const str = defineModel<string>('str')
      const optional = defineModel<string>('optional', { required: false })
      </script>
      `,
      { defineModel: true, isProd: true }
    )
    assertCode(content)
    expect(content).toMatch('"modelValue": { type: Boolean }')
    expect(content).toMatch('"fn": { type: Function as PropType<() => void> }')
    expect(content).toMatch(
      '"fnWithDefault": { type: Function as PropType<() => void>, ...{ default: () => null } },'
    )
    expect(content).toMatch('"str": { type: String }')
    expect(content).toMatch(
      '"optional": { type: String, ...{ required: false } }'
    )
    expect(content).toMatch(
      'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]'
    )
    expect(content).toMatch(
      `const modelValue = useModel<boolean>(__ins.props, "modelValue")`
    )
    expect(content).toMatch(
      `const fn = useModel<() => void>(__ins.props, "fn")`
    )
    expect(content).toMatch(`const str = useModel<string>(__ins.props, "str")`)
    expect(bindings).toStrictEqual({
      modelValue: BindingTypes.SETUP_REF,
      fn: BindingTypes.SETUP_REF,
      fnWithDefault: BindingTypes.SETUP_REF,
      str: BindingTypes.SETUP_REF,
      optional: BindingTypes.SETUP_REF,
    })
  })
})