]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: test for setup()
authorEvan You <yyx990803@gmail.com>
Mon, 26 Aug 2019 22:08:56 +0000 (18:08 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 26 Aug 2019 22:08:56 +0000 (18:08 -0400)
packages/runtime-core/__tests__/apiSetupContext.spec.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-dom/src/modules/props.ts
packages/runtime-test/src/index.ts
packages/runtime-test/src/serialize.ts

index 46b65e3a9def0cec52f6a2775866b0092a453239..796347b7cfaa5677e2836fc0dd06503c6d1bed59 100644 (file)
@@ -1,5 +1,174 @@
+import { ref, reactive } from '@vue/reactivity'
+import {
+  renderToString,
+  h,
+  nodeOps,
+  render,
+  serializeInner,
+  nextTick,
+  watch,
+  createComponent,
+  triggerEvent,
+  TestElement
+} from '@vue/runtime-test'
+
 // reference: https://vue-composition-api-rfc.netlify.com/api.html#setup
 
 describe('api: setup context', () => {
-  test.todo('should work')
+  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 `${this.ref} ${this.object.msg} ${this.value}`
+      }
+    }
+    expect(renderToString(h(Comp))).toMatch(`foo bar baz`)
+  })
+
+  it('should support returning render function', () => {
+    const Comp = {
+      setup() {
+        return () => {
+          return h('div', 'hello')
+        }
+      }
+    }
+    expect(renderToString(h(Comp))).toMatch(`hello`)
+  })
+
+  it('props', async () => {
+    const count = ref(0)
+    let dummy
+
+    const Parent = {
+      render: () => h(Child, { count: count.value })
+    }
+
+    const Child = createComponent({
+      setup(props: { count: number }) {
+        watch(() => {
+          dummy = props.count
+        })
+        return () => h('div', props.count)
+      }
+    })
+
+    const root = nodeOps.createElement('div')
+    render(h(Parent), root)
+    expect(serializeInner(root)).toMatch(`<div>0</div>`)
+    expect(dummy).toBe(0)
+
+    // props should be reactive
+    count.value++
+    await nextTick()
+    expect(serializeInner(root)).toMatch(`<div>1</div>`)
+    expect(dummy).toBe(1)
+  })
+
+  it('context.attrs', async () => {
+    const toggle = ref(true)
+
+    const Parent = {
+      render: () => h(Child, toggle.value ? { id: 'foo' } : { class: 'baz' })
+    }
+
+    const Child = {
+      // explicit empty props declaration
+      // puts everything received in attrs
+      props: {},
+      setup(props: any, { attrs }: any) {
+        return () => h('div', attrs)
+      }
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(Parent), root)
+    expect(serializeInner(root)).toMatch(`<div id="foo"></div>`)
+
+    // should update even though it's not reactive
+    toggle.value = false
+    await nextTick()
+    expect(serializeInner(root)).toMatch(`<div class="baz"></div>`)
+  })
+
+  it('context.slots', async () => {
+    const id = ref('foo')
+
+    const Parent = {
+      render: () =>
+        h(Child, null, {
+          foo: () => id.value,
+          bar: () => 'bar'
+        })
+    }
+
+    const Child = {
+      setup(props: any, { slots }: any) {
+        return () => h('div', [...slots.foo(), ...slots.bar()])
+      }
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(Parent), root)
+    expect(serializeInner(root)).toMatch(`<div>foobar</div>`)
+
+    // should update even though it's not reactive
+    id.value = 'baz'
+    await nextTick()
+    expect(serializeInner(root)).toMatch(`<div>bazbar</div>`)
+  })
+
+  it('context.emit', async () => {
+    const count = ref(0)
+    const spy = jest.fn()
+
+    const Parent = {
+      render: () =>
+        h(Child, {
+          count: count.value,
+          onInc: (newVal: number) => {
+            spy()
+            count.value = newVal
+          }
+        })
+    }
+
+    const Child = createComponent({
+      props: {
+        count: {
+          type: Number,
+          default: 1
+        }
+      },
+      setup(props, { emit }) {
+        return () =>
+          h(
+            'div',
+            {
+              onClick: () => emit('inc', props.count + 1)
+            },
+            props.count
+          )
+      }
+    })
+
+    const root = nodeOps.createElement('div')
+    render(h(Parent), root)
+    expect(serializeInner(root)).toMatch(`<div>0</div>`)
+
+    // emit should trigger parent handler
+    triggerEvent(root.children[0] as TestElement, 'click')
+    expect(spy).toHaveBeenCalled()
+    await nextTick()
+    expect(serializeInner(root)).toMatch(`<div>1</div>`)
+  })
 })
index ad363a3bf2de6a59f5fada4a64168cd3deef487b..21e5b443d6e221baaec30d0a792953d9d1e28fcb 100644 (file)
@@ -92,8 +92,6 @@ interface SetupContext {
   attrs: Data
   slots: Slots
   refs: Data
-  parent: ComponentInstance | null
-  root: ComponentInstance
   emit: ((event: string, ...args: unknown[]) => void)
 }
 
@@ -288,9 +286,7 @@ function createSetupContext(instance: ComponentInstance): SetupContext {
     attrs: new Proxy(instance, SetupProxyHandlers.attrs),
     slots: new Proxy(instance, SetupProxyHandlers.slots),
     refs: new Proxy(instance, SetupProxyHandlers.refs),
-    emit: instance.emit,
-    parent: instance.parent,
-    root: instance.root
+    emit: instance.emit
   } as any
   return __DEV__ ? Object.freeze(context) : context
 }
@@ -305,9 +301,7 @@ export function renderComponentRoot(instance: ComponentInstance): VNode {
     slots,
     attrs,
     refs,
-    emit,
-    parent,
-    root
+    emit
   } = instance
   if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
     return normalizeVNode(
@@ -322,9 +316,7 @@ export function renderComponentRoot(instance: ComponentInstance): VNode {
             attrs,
             slots,
             refs,
-            emit,
-            parent,
-            root
+            emit
           })
         : render(props, null as any)
     )
@@ -387,5 +379,6 @@ function hasPropsChanged(prevProps: Data, nextProps: Data): boolean {
       return true
     }
   }
+  console.log(111)
   return false
 }
index 792657425903ced1236365baaf1d9f68949d9e3d..745fcc8e141b9b9c4ec1201eb26791baf5d9637f 100644 (file)
@@ -184,7 +184,7 @@ export function resolveProps(
 
   instance.props = __DEV__ ? readonly(props) : props
   instance.attrs = options
-    ? __DEV__
+    ? __DEV__ && attrs != null
       ? readonly(attrs)
       : attrs
     : instance.props
index 8dd9c437677a125743bd1901fc0cb536a4e6a0f0..e9e363744b97443f39c633325878a13c4ef41b05 100644 (file)
@@ -12,5 +12,5 @@ export function patchDOMProp(
   if ((key === 'innerHTML' || key === 'textContent') && prevChildren != null) {
     unmountChildren(prevChildren, parentComponent)
   }
-  el[key] = value
+  el[key] = value == null ? '' : value
 }
index 4c211fd9f85cf56afdc40d214af44327d11a185a..9e1816f5ac2e0c1428c6c45915546844c3ecb9f9 100644 (file)
@@ -1,14 +1,22 @@
 import { createRenderer, VNode } from '@vue/runtime-core'
 import { nodeOps, TestElement } from './nodeOps'
 import { patchProp } from './patchProp'
+import { serializeInner } from './serialize'
 
 export const render = createRenderer({
   patchProp,
   ...nodeOps
 }) as (node: VNode | null, container: TestElement) => VNode
 
-export { serialize } from './serialize'
-export { triggerEvent } from './triggerEvent'
+// convenience for one-off render validations
+export function renderToString(vnode: VNode) {
+  const root = nodeOps.createElement('div')
+  render(vnode, root)
+  return serializeInner(root)
+}
+
+export * from './triggerEvent'
+export * from './serialize'
 export * from './nodeOps'
 export * from './jestUtils'
 export * from '@vue/runtime-core'
index fa401f4ad25cbc5db844e0de155a5837662df590..72ed03d2e0d09c8348857dc4b81ea0a344c85b10 100644 (file)
@@ -19,6 +19,19 @@ export function serialize(
   }
 }
 
+export function serializeInner(
+  node: TestElement,
+  indent: number = 0,
+  depth: number = 0
+) {
+  const newLine = indent ? `\n` : ``
+  return node.children.length
+    ? newLine +
+        node.children.map(c => serialize(c, indent, depth + 1)).join(newLine) +
+        newLine
+    : ``
+}
+
 function serializeElement(
   node: TestElement,
   indent: number,
@@ -26,19 +39,15 @@ function serializeElement(
 ): string {
   const props = Object.keys(node.props)
     .map(key => {
-      return isOn(key) ? `` : `${key}=${JSON.stringify(node.props[key])}`
+      const value = node.props[key]
+      return isOn(key) || value == null ? `` : `${key}=${JSON.stringify(value)}`
     })
+    .filter(_ => _)
     .join(' ')
-  const newLine = indent ? `\n` : ``
-  const children = node.children.length
-    ? newLine +
-      node.children.map(c => serialize(c, indent, depth + 1)).join(newLine) +
-      newLine
-    : ``
   const padding = indent ? ` `.repeat(indent).repeat(depth) : ``
   return (
     `${padding}<${node.tag}${props ? ` ${props}` : ``}>` +
-    `${children}` +
+    `${serializeInner(node, indent, depth)}` +
     `${padding}</${node.tag}>`
   )
 }