Prechádzať zdrojové kódy

test(runtime-vapor): add apiSetupContext unit test (#237)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
XiaoDong 1 rok pred
rodič
commit
bbde386a7c

+ 3 - 3
packages/compiler-vapor/src/generate.ts

@@ -123,10 +123,10 @@ export function generate(
     push('}')
     push('}')
   }
   }
 
 
-  const deligates = genDeligates(context)
+  const delegates = genDelegates(context)
   const templates = genTemplates(ir.template, context)
   const templates = genTemplates(ir.template, context)
   const imports = genHelperImports(context)
   const imports = genHelperImports(context)
-  const preamble = imports + templates + deligates
+  const preamble = imports + templates + delegates
 
 
   const newlineCount = [...preamble].filter(c => c === '\n').length
   const newlineCount = [...preamble].filter(c => c === '\n').length
   if (newlineCount && !inline) {
   if (newlineCount && !inline) {
@@ -148,7 +148,7 @@ export function generate(
   }
   }
 }
 }
 
 
-function genDeligates({ delegates, vaporHelper }: CodegenContext) {
+function genDelegates({ delegates, vaporHelper }: CodegenContext) {
   return delegates.size
   return delegates.size
     ? genCall(
     ? genCall(
         vaporHelper('delegateEvents'),
         vaporHelper('delegateEvents'),

+ 5 - 0
packages/runtime-vapor/__tests__/_utils.ts

@@ -52,6 +52,10 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
       return res()
       return res()
     }
     }
 
 
+    function html() {
+      return host.innerHTML
+    }
+
     const res = () => ({
     const res = () => ({
       component,
       component,
       host,
       host,
@@ -61,6 +65,7 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
       mount,
       mount,
       render,
       render,
       resetHost,
       resetHost,
+      html,
     })
     })
 
 
     return res()
     return res()

+ 213 - 0
packages/runtime-vapor/__tests__/apiSetupContext.spec.ts

@@ -0,0 +1,213 @@
+import {
+  createComponent,
+  createSlot,
+  createTextNode,
+  defineComponent,
+  delegate,
+  delegateEvents,
+  insert,
+  nextTick,
+  reactive,
+  ref,
+  renderEffect,
+  setDynamicProps,
+  template,
+  watchEffect,
+} from '../src'
+import { makeRender } from './_utils'
+
+const define = makeRender()
+
+describe('api: setup context', () => {
+  it('should expose return values to template render context', () => {
+    const { html } = define({
+      setup() {
+        return {
+          ref: ref('foo'),
+          object: reactive({ msg: 'bar' }),
+          value: 'baz',
+        }
+      },
+      render(ctx) {
+        return createTextNode([`${ctx.ref} ${ctx.object.msg} ${ctx.value}`])
+      },
+    }).render()
+    expect(html()).toMatch(`foo bar baz`)
+  })
+
+  it('should support returning render function', () => {
+    const { html } = define({
+      setup() {
+        return createTextNode([`hello`])
+      },
+    }).render()
+    expect(html()).toMatch(`hello`)
+  })
+
+  it('props', async () => {
+    const count = ref(0)
+    let dummy
+
+    const Child = defineComponent({
+      props: { count: Number },
+      setup(props) {
+        watchEffect(() => {
+          dummy = props.count
+        })
+        return createTextNode(() => [props.count])
+      },
+    })
+
+    const { html } = define({
+      render: () => createComponent(Child, { count: () => count.value }),
+    }).render()
+
+    expect(html()).toMatch(`0`)
+
+    count.value++
+    await nextTick()
+    expect(dummy).toBe(1)
+    expect(html()).toMatch(`1`)
+  })
+
+  it('context.attrs', async () => {
+    const toggle = ref(true)
+
+    const Child = defineComponent({
+      inheritAttrs: false,
+      setup(props, { attrs }) {
+        const el = document.createElement('div')
+        renderEffect(() => {
+          setDynamicProps(el, attrs)
+        })
+        return el
+      },
+    })
+
+    const { html } = define({
+      render: () =>
+        createComponent(Child, () =>
+          toggle.value ? { id: 'foo' } : { class: 'baz' },
+        ),
+    }).render()
+
+    expect(html()).toMatch(`<div id="foo"></div>`)
+
+    toggle.value = false
+    await nextTick()
+    expect(html()).toMatch(`<div class="baz"></div>`)
+  })
+
+  // #4161
+  it('context.attrs in child component slots', async () => {
+    const toggle = ref(true)
+
+    const Wrapper = defineComponent({
+      setup(_, { slots }) {
+        return slots.default!()
+      },
+    })
+
+    const Child = defineComponent({
+      inheritAttrs: false,
+      setup(_: any, { attrs }: any) {
+        return createComponent(Wrapper, null, {
+          default: () => {
+            const n0 = template('<div>')() as HTMLDivElement
+            renderEffect(() => {
+              setDynamicProps(n0, attrs)
+            })
+            return n0
+          },
+        })
+      },
+    })
+
+    const { html } = define({
+      render: () =>
+        createComponent(Child, () =>
+          toggle.value ? { id: 'foo' } : { class: 'baz' },
+        ),
+    }).render()
+
+    expect(html()).toMatch(`<div id="foo"></div>`)
+
+    // should update even though it's not reactive
+    toggle.value = false
+    await nextTick()
+    expect(html()).toMatch(`<div class="baz"></div>`)
+  })
+
+  it('context.slots', async () => {
+    const id = ref('foo')
+
+    const Child = defineComponent({
+      render() {
+        return [createSlot('foo'), createSlot('bar')]
+      },
+    })
+
+    const { html } = define({
+      render() {
+        return createComponent(Child, null, null, [
+          () => ({
+            name: 'foo',
+            fn: () => createTextNode(() => [id.value]),
+          }),
+          () => ({
+            name: 'bar',
+            fn: () => createTextNode(['bar']),
+          }),
+        ])
+      },
+    }).render()
+
+    expect(html()).toMatch(`foo<!--slot-->bar<!--slot-->`)
+
+    id.value = 'baz'
+    await nextTick()
+    expect(html()).toMatch(`baz<!--slot-->bar<!--slot-->`)
+  })
+
+  it('context.emit', async () => {
+    const count = ref(0)
+    const spy = vi.fn()
+
+    delegateEvents('click')
+
+    const Child = defineComponent({
+      props: {
+        count: { type: Number, default: 1 },
+      },
+      setup(props, { emit }) {
+        const n0 = template('<div>')() as HTMLDivElement
+        delegate(n0, 'click', () => () => {
+          emit('inc', props.count + 1)
+        })
+        insert(
+          createTextNode(() => [props.count]),
+          n0,
+        )
+        return n0
+      },
+    })
+
+    const { host, html } = define({
+      render: () =>
+        createComponent(Child, {
+          count: () => count.value,
+          onInc: () => (newVal: number) => {
+            spy()
+            count.value = newVal
+          },
+        }),
+    }).render()
+
+    expect(html()).toMatch(`<div>0</div>`)
+    ;(host.children[0] as HTMLDivElement).click()
+
+    expect(spy).toHaveBeenCalled()
+    await nextTick()
+    expect(html()).toMatch(`<div>1</div>`)
+  })
+})