Back to Repositories

Testing v-on Event Handling Transformations in uni-app MP-Alipay Platform

This test suite validates the v-on event handling transformations for the Alipay Mini Program platform in uni-app. It ensures proper event binding, authorization handling, and event naming conventions for the MP-Alipay runtime environment.

Test Coverage Overview

The test suite provides comprehensive coverage of v-on directive transformations for Alipay Mini Program.

Key areas tested include:
  • Phone number authorization handling
  • Basic event binding transformations
  • Dynamic event argument handling
  • Inline statement processing
  • Complex expression handling
  • Event naming case conversions

Implementation Analysis

The testing approach uses a custom assert utility to verify template transformations and runtime code generation. Tests validate both the template compilation output and the generated render functions, ensuring correct event binding syntax for the Alipay platform.

Key patterns include transformation of v-on directives to Alipay’s onXXX attributes and proper handling of event handler scoping.

Technical Details

Testing tools and setup:
  • Jest as the test runner
  • Custom assert utility for template transformation validation
  • Template compiler integration testing
  • Runtime code generation verification
  • Prefixed identifiers handling
  • Cache handler optimizations

Best Practices Demonstrated

The test suite demonstrates several testing best practices:

  • Isolated test cases for specific functionality
  • Comprehensive edge case coverage
  • Clear test organization by feature area
  • Validation of both compilation and runtime behavior
  • Consistent assertion patterns
  • Thorough documentation of test cases

dcloudio/uni-app

packages/uni-mp-alipay/__tests__/vOn.spec.ts

            
import { assert } from './testUtils'

describe('mp-alipay: transform v-on', () => {
  test('getphonenumber', () => {
    assert(
      `<button open-type='getPhoneNumber' @getphonenumber="getPhoneNumber">获取手机号</button>`,
      `<button open-type="getAuthorize" scope="phoneNumber" onGetAuthorize="{{a}}" onError="{{b}}">获取手机号</button>`,
      `(_ctx, _cache) => {
  return { a: _o($event => _ctx.$onAliGetAuthorize(_ctx.getPhoneNumber, $event)), b: _o($event => _ctx.$onAliAuthError(_ctx.getPhoneNumber, $event)) }
}`
    )
    assert(
      `<button open-type='getPhoneNumber' @getphonenumber="getPhoneNumber($event)">获取手机号</button>`,
      `<button open-type="getAuthorize" scope="phoneNumber" onGetAuthorize="{{a}}" onError="{{b}}">获取手机号</button>`,
      `(_ctx, _cache) => {
  return { a: _o($event => _ctx.$onAliGetAuthorize($event => { _ctx.getPhoneNumber($event); }, $event)), b: _o($event => _ctx.$onAliAuthError($event => { _ctx.getPhoneNumber($event); }, $event)) }
}`
    )
  })
  test('basic', () => {
    assert(
      `<view v-on:click="onClick"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o(_ctx.onClick) }
}`
    )
    assert(
      `<custom v-on:click="onClick"/>`,
      `<custom onClick="{{a}}" u-i="2a9ec0b0-0" onVI="__l"/>`,
      `(_ctx, _cache) => {
  return { a: _o(_ctx.onClick) }
}`
    )
  })
  test('dynamic arg', () => {
    // <view v-on:[event]="handler"/>
  })
  test('dynamic arg with prefixing', () => {
    // <view v-on:[event]="handler"/>
  })
  test('dynamic arg with complex exp prefixing', () => {
    // <view v-on:[event(foo)]="handler"/>
  })
  test('should wrap as function if expression is inline statement', () => {
    assert(
      `<view @click="i++"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o($event => _ctx.i++) }
}`
    )
  })
  test('should handle multiple inline statement', () => {
    assert(
      `<view @click="foo();bar()"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o($event => { _ctx.foo(); _ctx.bar(); }) }
}`
    )
  })
  test('should handle multi-line statement', () => {
    assert(
      `<view @click="\nfoo();\nbar()\n"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  with (_ctx) {
    const { o: _o } = _Vue

    return { a: _o($event => { foo(); bar(); }) }
  }
}`,
      { prefixIdentifiers: false, mode: 'function' }
    )
  })
  test('inline statement w/ prefixIdentifiers: true', () => {
    assert(
      `<view @click="foo($event)"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o($event => _ctx.foo($event)) }
}`
    )
  })
  test('multiple inline statements w/ prefixIdentifiers: true', () => {
    assert(
      `<view @click="foo($event);bar()"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o($event => { _ctx.foo($event); _ctx.bar(); }) }
}`
    )
  })
  test('should NOT wrap as function if expression is already function expression', () => {
    assert(
      `<view @click="$event => foo($event)"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o($event => _ctx.foo($event)) }
}`
    )
  })
  test('should NOT wrap as function if expression is already function expression (with newlines)', () => {
    assert(
      `<view @click="
  $event => {
    foo($event)
  }
"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o($event => { _ctx.foo($event); }) }
}`
    )
  })
  test('should NOT wrap as function if expression is already function expression (with newlines + function keyword)', () => {
    assert(
      `<view @click="
  function($event) {
    foo($event)
  }
"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o(function ($event) { _ctx.foo($event); }) }
}`
    )
  })
  test('should NOT wrap as function if expression is complex member expression', () => {
    assert(
      `<view @click="a['b' + c]"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  with (_ctx) {
    const { o: _o } = _Vue

    return { a: _o(a['b' + c]) }
  }
}`,
      {
        prefixIdentifiers: false,
        mode: 'function',
      }
    )
  })
  test('complex member expression w/ prefixIdentifiers: true', () => {
    assert(
      `<view @click="a['b' + c]"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o(_ctx.a['b' + _ctx.c]) }
}`
    )
  })
  test('function expression w/ prefixIdentifiers: true', () => {
    assert(
      `<view @click="e => foo(e)"/>`,
      `<view onTap="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o(e => _ctx.foo(e)) }
}`
    )
  })

  test('case conversion for kebab-case events', () => {
    assert(
      `<view v-on:foo-bar="onMount"/>`,
      `<view onFooBar="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o(_ctx.onMount) }
}`
    )
  })

  test('case conversion for vnode hooks', () => {
    assert(
      `<view v-on:vnode-mounted="onMount"/>`,
      `<view onVnodeMounted="{{a}}"/>`,
      `(_ctx, _cache) => {
  return { a: _o(_ctx.onMount) }
}`
    )
  })

  describe('cacheHandler', () => {
    test('empty handler', () => {
      assert(
        `<view v-on:click.prevent />`,
        `<view catchTap="{{a}}"/>`,
        `(_ctx, _cache) => {
  return { a: _o(() => {}) }
}`
      )
    })

    test('member expression handler', () => {
      // <div v-on:click="foo" />
    })

    test('compound member expression handler', () => {
      // <div v-on:click="foo.bar" />
    })

    test('bail on component member expression handler', () => {
      // <comp v-on:click="foo" />
    })

    test('should not be cached inside v-once', () => {
      // <div v-once><div v-on:click="foo"/></div>
    })

    test('inline function expression handler', () => {
      // <div v-on:click="() => foo()" />
    })

    test('inline async arrow function expression handler', () => {
      // <div v-on:click="async () => await foo()" />
    })

    test('inline async function expression handler', () => {
      // <div v-on:click="async function () { await foo() } " />
    })

    test('inline statement handler', () => {
      // <div v-on:click="foo++" />
    })
  })
})