| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- import { h, ref, reactive, isReactive, toRef, isRef } from 'v3'
- import { nextTick } from 'core/util'
- import { effect } from 'v3/reactivity/effect'
- import Vue from 'vue'
- function renderToString(comp: any) {
- const vm = new Vue(comp).$mount()
- return vm.$el.outerHTML
- }
- describe('api: setup context', () => {
- it('should expose return values to template render context', () => {
- const Comp = {
- setup() {
- return {
- // ref should auto-unwrap
- ref: ref('foo'),
- // object exposed as-is
- object: reactive({ msg: 'bar' }),
- // primitive value exposed as-is
- value: 'baz'
- }
- },
- render() {
- return h('div', `${this.ref} ${this.object.msg} ${this.value}`)
- }
- }
- expect(renderToString(Comp)).toMatch(`<div>foo bar baz</div>`)
- })
- it('should support returning render function', () => {
- const Comp = {
- setup() {
- return () => {
- return h('div', 'hello')
- }
- }
- }
- expect(renderToString(Comp)).toMatch(`<div>hello</div>`)
- })
- it('props', async () => {
- const count = ref(0)
- let dummy
- const Parent = {
- render: () => h(Child, { props: { count: count.value } })
- }
- const Child = {
- props: { count: Number },
- setup(props) {
- effect(() => {
- dummy = props.count
- })
- return () => h('div', props.count)
- }
- }
- const vm = new Vue(Parent).$mount()
- expect(vm.$el.outerHTML).toMatch(`<div>0</div>`)
- expect(dummy).toBe(0)
- // props should be reactive
- count.value++
- await nextTick()
- expect(vm.$el.outerHTML).toMatch(`<div>1</div>`)
- expect(dummy).toBe(1)
- })
- it('context.attrs', async () => {
- const toggle = ref(true)
- const Parent = {
- render: () =>
- h(Child, { attrs: toggle.value ? { id: 'foo' } : { class: 'baz' } })
- }
- const Child = {
- // explicit empty props declaration
- // puts everything received in attrs
- // disable implicit fallthrough
- inheritAttrs: false,
- setup(_props: any, { attrs }: any) {
- return () => h('div', { attrs })
- }
- }
- const vm = new Vue(Parent).$mount()
- expect(vm.$el.outerHTML).toMatch(`<div id="foo"></div>`)
- // should update even though it's not reactive
- toggle.value = false
- await nextTick()
- expect(vm.$el.outerHTML).toMatch(`<div class="baz"></div>`)
- })
- // vuejs/core #4161
- it('context.attrs in child component slots', async () => {
- const toggle = ref(true)
- const Wrapper = {
- template: `<div><slot/></div>`
- }
- const Child = {
- inheritAttrs: false,
- setup(_: any, { attrs }: any) {
- return () => {
- return h(Wrapper, [h('div', { attrs })])
- }
- }
- }
- const Parent = {
- render: () =>
- h(Child, { attrs: toggle.value ? { id: 'foo' } : { class: 'baz' } })
- }
- const vm = new Vue(Parent).$mount()
- expect(vm.$el.outerHTML).toMatch(`<div id="foo"></div>`)
- // should update even though it's not reactive
- toggle.value = false
- await nextTick()
- expect(vm.$el.outerHTML).toMatch(`<div class="baz"></div>`)
- })
- it('context.attrs in child component scoped slots', async () => {
- const toggle = ref(true)
- const Wrapper = {
- template: `<div><slot/></div>`
- }
- const Child = {
- inheritAttrs: false,
- setup(_: any, { attrs }: any) {
- return () => {
- return h(Wrapper, {
- scopedSlots: {
- default: () => h('div', { attrs })
- }
- })
- }
- }
- }
- const Parent = {
- render: () =>
- h(Child, { attrs: toggle.value ? { id: 'foo' } : { class: 'baz' } })
- }
- const vm = new Vue(Parent).$mount()
- expect(vm.$el.outerHTML).toMatch(`<div id="foo"></div>`)
- // should update even though it's not reactive
- toggle.value = false
- await nextTick()
- expect(vm.$el.outerHTML).toMatch(`<div class="baz"></div>`)
- })
- it('context.slots', async () => {
- const id = ref('foo')
- const Child = {
- setup(_props: any, { slots }: any) {
- // #12672 behavior consistency with Vue 3: should be able to access
- // slots directly in setup()
- expect(slots.foo()).toBeTruthy()
- return () => h('div', [...slots.foo(), ...slots.bar()])
- }
- }
- const Parent = {
- components: { Child },
- setup() {
- return { id }
- },
- template: `<Child>
- <template #foo>{{ id }}</template>
- <template #bar>bar</template>
- </Child>`
- }
- const vm = new Vue(Parent).$mount()
- expect(vm.$el.outerHTML).toMatch(`<div>foobar</div>`)
- // should update even though it's not reactive
- id.value = 'baz'
- await nextTick()
- expect(vm.$el.outerHTML).toMatch(`<div>bazbar</div>`)
- })
- it('context.emit', async () => {
- const count = ref(0)
- const spy = vi.fn()
- const Child = {
- props: {
- count: {
- type: Number,
- default: 1
- }
- },
- setup(props, { emit }) {
- return () =>
- h(
- 'div',
- {
- on: { click: () => emit('inc', props.count + 1) }
- },
- props.count
- )
- }
- }
- const Parent = {
- components: { Child },
- setup: () => ({
- count,
- onInc(newVal: number) {
- spy()
- count.value = newVal
- }
- }),
- template: `<Child :count="count" @inc="onInc" />`
- }
- const vm = new Vue(Parent).$mount()
- expect(vm.$el.outerHTML).toMatch(`<div>0</div>`)
- // emit should trigger parent handler
- triggerEvent(vm.$el as HTMLElement, 'click')
- expect(spy).toHaveBeenCalled()
- await nextTick()
- expect(vm.$el.outerHTML).toMatch(`<div>1</div>`)
- })
- it('directive resolution', () => {
- const spy = vi.fn()
- new Vue({
- setup: () => ({
- __sfc: true,
- vDir: {
- inserted: spy
- }
- }),
- template: `<div v-dir />`
- }).$mount()
- expect(spy).toHaveBeenCalled()
- })
- // #12743
- it('directive resolution for shorthand', () => {
- const spy = vi.fn()
- new Vue({
- setup: () => ({
- __sfc: true,
- vDir: spy
- }),
- template: `<div v-dir />`
- }).$mount()
- expect(spy).toHaveBeenCalled()
- })
-
- // #12561
- it('setup props should be reactive', () => {
- const msg = ref('hi')
- const Child = {
- props: ['msg'],
- setup: props => {
- expect(isReactive(props)).toBe(true)
- expect(isRef(toRef(props, 'foo'))).toBe(true)
- return () => {}
- }
- }
- new Vue({
- setup() {
- return h => h(Child, { props: { msg } })
- }
- }).$mount()
- })
- it('should not track dep accessed in setup', async () => {
- const spy = vi.fn()
- const msg = ref('hi')
- const Child = {
- setup: () => {
- msg.value
- return () => {}
- }
- }
- new Vue({
- setup() {
- return h => {
- spy()
- return h(Child)
- }
- }
- }).$mount()
- expect(spy).toHaveBeenCalledTimes(1)
- msg.value = 'bye'
- await nextTick()
- expect(spy).toHaveBeenCalledTimes(1)
- })
- it('context.listeners', async () => {
- let _listeners
- const Child = {
- setup(_, { listeners }) {
- _listeners = listeners
- return () => {}
- }
- }
- const Parent = {
- data: () => ({ log: () => 1 }),
- template: `<Child @foo="log" />`,
- components: { Child }
- }
- const vm = new Vue(Parent).$mount()
- expect(_listeners.foo()).toBe(1)
- vm.log = () => 2
- await nextTick()
- expect(_listeners.foo()).toBe(2)
- })
- })
|