| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- // NOTE: this test cases are based on paclages/runtime-core/__tests__/componentEmits.spec.ts
- // Note: emits and listener fallthrough is tested in
- // ./rendererAttrsFallthrough.spec.ts.
- import {
- isEmitListener,
- nextTick,
- onBeforeUnmount,
- ref,
- toHandlers,
- } from '@vue/runtime-dom'
- import {
- createComponent,
- createIf,
- defineVaporComponent,
- template,
- } from '../src'
- import { makeRender } from './_utils'
- const define = makeRender()
- describe('component: emit', () => {
- test('trigger handlers', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('foo')
- emit('bar')
- emit('!baz')
- return []
- },
- })
- const onFoo = vi.fn()
- const onBar = vi.fn()
- const onBaz = vi.fn()
- render({
- onfoo: () => onFoo,
- onBar: () => onBar,
- ['on!baz']: () => onBaz,
- })
- expect(onFoo).not.toHaveBeenCalled()
- expect(onBar).toHaveBeenCalled()
- expect(onBaz).toHaveBeenCalled()
- })
- test('trigger dynamic emits', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('foo')
- emit('bar')
- emit('!baz')
- return []
- },
- })
- const onFoo = vi.fn()
- const onBar = vi.fn()
- const onBaz = vi.fn()
- render({
- onfoo: () => onFoo,
- onBar: () => onBar,
- ['on!baz']: () => onBaz,
- })
- expect(onFoo).not.toHaveBeenCalled()
- expect(onBar).toHaveBeenCalled()
- expect(onBaz).toHaveBeenCalled()
- })
- test('trigger camelCase handler', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('test-event')
- return []
- },
- })
- const fooSpy = vi.fn()
- render({ onTestEvent: () => fooSpy })
- expect(fooSpy).toHaveBeenCalled()
- })
- test('trigger kebab-case handler', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('test-event')
- return []
- },
- })
- const fooSpy = vi.fn()
- render({ ['onTest-event']: () => fooSpy })
- expect(fooSpy).toHaveBeenCalledTimes(1)
- })
- test('trigger mixed case handlers', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('test-event')
- emit('testEvent')
- return []
- },
- })
- const fooSpy = vi.fn()
- const barSpy = vi.fn()
- render(
- toHandlers({
- 'test-event': () => fooSpy,
- testEvent: () => barSpy,
- }),
- )
- expect(fooSpy).toHaveBeenCalledTimes(1)
- expect(barSpy).toHaveBeenCalledTimes(1)
- })
- // for v-model:foo-bar usage in DOM templates
- test('trigger hyphenated events for update:xxx events', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('update:fooProp')
- emit('update:barProp')
- return []
- },
- })
- const fooSpy = vi.fn()
- const barSpy = vi.fn()
- render({
- ['onUpdate:fooProp']: () => fooSpy,
- ['onUpdate:bar-prop']: () => barSpy,
- })
- expect(fooSpy).toHaveBeenCalled()
- expect(barSpy).toHaveBeenCalled()
- })
- test('should trigger array of listeners', async () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('foo', 1)
- return []
- },
- })
- const fn1 = vi.fn()
- const fn2 = vi.fn()
- render({ onFoo: () => [fn1, fn2] })
- expect(fn1).toHaveBeenCalledTimes(1)
- expect(fn1).toHaveBeenCalledWith(1)
- expect(fn2).toHaveBeenCalledTimes(1)
- expect(fn2).toHaveBeenCalledWith(1)
- })
- test('warning for undeclared event (array)', () => {
- const { render } = define({
- emits: ['foo'],
- setup(_, { emit }) {
- emit('bar')
- return []
- },
- })
- render()
- expect(
- `Component emitted event "bar" but it is neither declared`,
- ).toHaveBeenWarned()
- })
- test('warning for undeclared event (object)', () => {
- const { render } = define({
- emits: {
- foo: null,
- },
- setup(_, { emit }) {
- emit('bar')
- return []
- },
- })
- render()
- expect(
- `Component emitted event "bar" but it is neither declared`,
- ).toHaveBeenWarned()
- })
- test('should not warn if has equivalent onXXX prop', () => {
- define({
- props: ['onFoo'],
- emits: [],
- setup(_, { emit }) {
- emit('foo')
- return []
- },
- }).render()
- expect(
- `Component emitted event "foo" but it is neither declared`,
- ).not.toHaveBeenWarned()
- })
- test('validator warning', () => {
- define({
- emits: {
- foo: (arg: number) => arg > 0,
- },
- setup(_, { emit }) {
- emit('foo', -1)
- return []
- },
- }).render()
- expect(`event validation failed for event "foo"`).toHaveBeenWarned()
- })
- test('.once', () => {
- const { render } = define({
- emits: {
- foo: null,
- bar: null,
- },
- setup(_, { emit }) {
- emit('foo')
- emit('foo')
- emit('bar')
- emit('bar')
- return []
- },
- })
- const fn = vi.fn()
- const barFn = vi.fn()
- render({
- onFooOnce: () => fn,
- onBarOnce: () => barFn,
- })
- expect(fn).toHaveBeenCalledTimes(1)
- expect(barFn).toHaveBeenCalledTimes(1)
- })
- test('.once with normal listener of the same name', () => {
- const { render } = define({
- emits: {
- foo: null,
- },
- setup(_, { emit }) {
- emit('foo')
- emit('foo')
- return []
- },
- })
- const onFoo = vi.fn()
- const onFooOnce = vi.fn()
- render({
- onFoo: () => onFoo,
- onFooOnce: () => onFooOnce,
- })
- expect(onFoo).toHaveBeenCalledTimes(2)
- expect(onFooOnce).toHaveBeenCalledTimes(1)
- })
- test('.number modifier should work with v-model on component', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('update:modelValue', '1')
- emit('update:foo', '2')
- return []
- },
- })
- const fn1 = vi.fn()
- const fn2 = vi.fn()
- render({
- modelValue: () => null,
- modelModifiers: () => ({ number: true }),
- ['onUpdate:modelValue']: () => fn1,
- foo: () => null,
- fooModifiers: () => ({ number: true }),
- ['onUpdate:foo']: () => fn2,
- })
- expect(fn1).toHaveBeenCalledTimes(1)
- expect(fn1).toHaveBeenCalledWith(1)
- expect(fn2).toHaveBeenCalledTimes(1)
- expect(fn2).toHaveBeenCalledWith(2)
- })
- test('.trim modifier should work with v-model on component', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('update:modelValue', ' one ')
- emit('update:foo', ' two ')
- return []
- },
- })
- const fn1 = vi.fn()
- const fn2 = vi.fn()
- render({
- modelValue() {
- return null
- },
- modelModifiers() {
- return { trim: true }
- },
- ['onUpdate:modelValue']() {
- return fn1
- },
- foo() {
- return null
- },
- fooModifiers() {
- return { trim: true }
- },
- 'onUpdate:foo'() {
- return fn2
- },
- })
- expect(fn1).toHaveBeenCalledTimes(1)
- expect(fn1).toHaveBeenCalledWith('one')
- expect(fn2).toHaveBeenCalledTimes(1)
- expect(fn2).toHaveBeenCalledWith('two')
- })
- test('.trim and .number modifiers should work with v-model on component', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('update:modelValue', ' +01.2 ')
- emit('update:foo', ' 1 ')
- return []
- },
- })
- const fn1 = vi.fn()
- const fn2 = vi.fn()
- render({
- modelValue() {
- return null
- },
- modelModifiers() {
- return { trim: true, number: true }
- },
- ['onUpdate:modelValue']() {
- return fn1
- },
- foo() {
- return null
- },
- fooModifiers() {
- return { trim: true, number: true }
- },
- ['onUpdate:foo']() {
- return fn2
- },
- })
- expect(fn1).toHaveBeenCalledTimes(1)
- expect(fn1).toHaveBeenCalledWith(1.2)
- expect(fn2).toHaveBeenCalledTimes(1)
- expect(fn2).toHaveBeenCalledWith(1)
- })
- test('only trim string parameter when work with v-model on component', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('update:modelValue', ' foo ', { bar: ' bar ' })
- return []
- },
- })
- const fn = vi.fn()
- render({
- modelValue() {
- return null
- },
- modelModifiers() {
- return { trim: true }
- },
- ['onUpdate:modelValue']() {
- return fn
- },
- })
- expect(fn).toHaveBeenCalledTimes(1)
- expect(fn).toHaveBeenCalledWith('foo', { bar: ' bar ' })
- })
- test('isEmitListener', () => {
- const options = {
- get click() {
- return null
- },
- get 'test-event'() {
- return null
- },
- get fooBar() {
- return null
- },
- get FooBaz() {
- return null
- },
- }
- expect(isEmitListener(options, 'onClick')).toBe(true)
- expect(isEmitListener(options, 'onclick')).toBe(false)
- expect(isEmitListener(options, 'onBlick')).toBe(false)
- // .once listeners
- expect(isEmitListener(options, 'onClickOnce')).toBe(true)
- expect(isEmitListener(options, 'onclickOnce')).toBe(false)
- // kebab-case option
- expect(isEmitListener(options, 'onTestEvent')).toBe(true)
- // camelCase option
- expect(isEmitListener(options, 'onFooBar')).toBe(true)
- // PascalCase option
- expect(isEmitListener(options, 'onFooBaz')).toBe(true)
- })
- test('does not emit after unmount', async () => {
- const fn = vi.fn()
- const Foo = defineVaporComponent({
- emits: ['closing'],
- setup(_, { emit }) {
- onBeforeUnmount(async () => {
- await nextTick()
- emit('closing', true)
- })
- return []
- },
- })
- const { app } = define(() =>
- createComponent(Foo, { onClosing: () => fn }),
- ).render()
- await nextTick()
- app.unmount()
- await nextTick()
- expect(fn).not.toHaveBeenCalled()
- })
- test('should not execute handler during lookup', () => {
- const { render } = define({
- setup(_, { emit }) {
- emit('click')
- return []
- },
- })
- const handler = vi.fn()
- const props = {
- $: [
- () => ({
- onClick: handler,
- }),
- ],
- }
- render(props as any)
- expect(handler).toHaveBeenCalledTimes(1)
- })
- test('should re-queue when child emit mutates parent state during update', async () => {
- const show = ref(false)
- const calls: string[] = []
- const { component: Child } = define({
- emits: ['change'],
- setup(_: any, { emit }: any) {
- emit('change')
- return template('<p>child</p>')()
- },
- })
- const { host } = define({
- setup() {
- const onChange = () => {
- calls.push(`change:${show.value}`)
- show.value = false
- }
- return createIf(
- () => show.value,
- () =>
- createComponent(Child, {
- onChange: () => onChange,
- }),
- )
- },
- }).render()
- show.value = true
- await nextTick()
- expect(calls).toEqual(['change:true'])
- expect(host.innerHTML).toBe('<!--if-->')
- })
- })
|