]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(ssr): test for rendering components
authorEvan You <yyx990803@gmail.com>
Wed, 29 Jan 2020 21:46:18 +0000 (16:46 -0500)
committerEvan You <yyx990803@gmail.com>
Wed, 29 Jan 2020 21:46:18 +0000 (16:46 -0500)
packages/runtime-core/src/apiOptions.ts
packages/server-renderer/__tests__/renderToString.spec.ts
packages/server-renderer/src/renderToString.ts
tsconfig.json

index 7216edafb7c3fbf8b1432988066c3e2d3a180509..57094e1aaaf36c3b0bbfd6b64f17d450b49cab42 100644 (file)
@@ -64,7 +64,12 @@ export interface ComponentOptionsBase<
   // type.
   render?: Function
   // SSR only. This is produced by compiler-ssr and attached in compiler-sfc
-  ssrRender?: Function
+  // not user facing, so the typing is lax and for test only.
+  ssrRender?: (
+    ctx: any,
+    push: (item: any) => void,
+    parentInstance: ComponentInternalInstance
+  ) => void
   components?: Record<
     string,
     Component | { new (): ComponentPublicInstance<any, any, any, any, any> }
index 3a7d76d4d3b85e4976f80b4fe5b8a91823569fd7..ffab23a078443fa4a5d440a2752e1a115d5e0292 100644 (file)
-// import { renderToString, renderComponent } from '../src'
+import { createApp, h } from 'vue'
+import { renderToString, renderComponent, renderSlot } from '../src'
 
 describe('ssr: renderToString', () => {
-  describe('elements', () => {
-    test('text children', () => {})
+  describe('components', () => {
+    test('vnode components', async () => {
+      expect(
+        await renderToString(
+          createApp({
+            data() {
+              return { msg: 'hello' }
+            },
+            render(this: any) {
+              return h('div', this.msg)
+            }
+          })
+        )
+      ).toBe(`<div>hello</div>`)
+    })
 
-    test('array children', () => {})
+    test('optimized components', async () => {
+      expect(
+        await renderToString(
+          createApp({
+            data() {
+              return { msg: 'hello' }
+            },
+            ssrRender(ctx, push) {
+              push(`<div>${ctx.msg}</div>`)
+            }
+          })
+        )
+      ).toBe(`<div>hello</div>`)
+    })
 
-    test('void elements', () => {})
+    test('nested vnode components', async () => {
+      const Child = {
+        props: ['msg'],
+        render(this: any) {
+          return h('div', this.msg)
+        }
+      }
 
-    test('innerHTML', () => {})
+      expect(
+        await renderToString(
+          createApp({
+            render() {
+              return h('div', ['parent', h(Child, { msg: 'hello' })])
+            }
+          })
+        )
+      ).toBe(`<div>parent<div>hello</div></div>`)
+    })
 
-    test('textContent', () => {})
+    test('nested optimized components', async () => {
+      const Child = {
+        props: ['msg'],
+        ssrRender(ctx: any, push: any) {
+          push(`<div>${ctx.msg}</div>`)
+        }
+      }
 
-    test('textarea value', () => {})
+      expect(
+        await renderToString(
+          createApp({
+            ssrRender(_ctx, push, parent) {
+              push(`<div>parent`)
+              push(renderComponent(Child, { msg: 'hello' }, null, parent))
+              push(`</div>`)
+            }
+          })
+        )
+      ).toBe(`<div>parent<div>hello</div></div>`)
+    })
+
+    test('mixing optimized / vnode components', async () => {
+      const OptimizedChild = {
+        props: ['msg'],
+        ssrRender(ctx: any, push: any) {
+          push(`<div>${ctx.msg}</div>`)
+        }
+      }
+
+      const VNodeChild = {
+        props: ['msg'],
+        render(this: any) {
+          return h('div', this.msg)
+        }
+      }
+
+      expect(
+        await renderToString(
+          createApp({
+            ssrRender(_ctx, push, parent) {
+              push(`<div>parent`)
+              push(
+                renderComponent(OptimizedChild, { msg: 'opt' }, null, parent)
+              )
+              push(renderComponent(VNodeChild, { msg: 'vnode' }, null, parent))
+              push(`</div>`)
+            }
+          })
+        )
+      ).toBe(`<div>parent<div>opt</div><div>vnode</div></div>`)
+    })
+
+    test('nested components with optimized slots', async () => {
+      const Child = {
+        props: ['msg'],
+        ssrRender(ctx: any, push: any, parent: any) {
+          push(`<div class="child">`)
+          renderSlot(ctx.$slots.default, { msg: 'from slot' }, push, parent)
+          push(`</div>`)
+        }
+      }
+
+      expect(
+        await renderToString(
+          createApp({
+            ssrRender(_ctx, push, parent) {
+              push(`<div>parent`)
+              push(
+                renderComponent(
+                  Child,
+                  { msg: 'hello' },
+                  {
+                    // optimized slot using string push
+                    default: ({ msg }: any, push: any) => {
+                      push(`<span>${msg}</span>`)
+                    },
+                    _compiled: true // important to avoid slots being normalized
+                  },
+                  parent
+                )
+              )
+              push(`</div>`)
+            }
+          })
+        )
+      ).toBe(
+        `<div>parent<div class="child">` +
+          `<!----><span>from slot</span><!---->` +
+          `</div></div>`
+      )
+    })
+
+    test('nested components with vnode slots', async () => {
+      const Child = {
+        props: ['msg'],
+        ssrRender(ctx: any, push: any, parent: any) {
+          push(`<div class="child">`)
+          renderSlot(ctx.$slots.default, { msg: 'from slot' }, push, parent)
+          push(`</div>`)
+        }
+      }
+
+      expect(
+        await renderToString(
+          createApp({
+            ssrRender(_ctx, push, parent) {
+              push(`<div>parent`)
+              push(
+                renderComponent(
+                  Child,
+                  { msg: 'hello' },
+                  {
+                    // bailed slots returning raw vnodes
+                    default: ({ msg }: any) => {
+                      return h('span', msg)
+                    }
+                  },
+                  parent
+                )
+              )
+              push(`</div>`)
+            }
+          })
+        )
+      ).toBe(
+        `<div>parent<div class="child">` +
+          `<!----><span>from slot</span><!---->` +
+          `</div></div>`
+      )
+    })
+
+    test('async components', async () => {
+      const Child = {
+        // should wait for resovled render context from setup()
+        async setup() {
+          return {
+            msg: 'hello'
+          }
+        },
+        ssrRender(ctx: any, push: any) {
+          push(`<div>${ctx.msg}</div>`)
+        }
+      }
+
+      expect(
+        await renderToString(
+          createApp({
+            ssrRender(_ctx, push, parent) {
+              push(`<div>parent`)
+              push(renderComponent(Child, null, null, parent))
+              push(`</div>`)
+            }
+          })
+        )
+      ).toBe(`<div>parent<div>hello</div></div>`)
+    })
+
+    test('parallel async components', async () => {
+      const OptimizedChild = {
+        props: ['msg'],
+        async setup(props: any) {
+          return {
+            localMsg: props.msg + '!'
+          }
+        },
+        ssrRender(ctx: any, push: any) {
+          push(`<div>${ctx.localMsg}</div>`)
+        }
+      }
+
+      const VNodeChild = {
+        props: ['msg'],
+        async setup(props: any) {
+          return {
+            localMsg: props.msg + '!'
+          }
+        },
+        render(this: any) {
+          return h('div', this.localMsg)
+        }
+      }
+
+      expect(
+        await renderToString(
+          createApp({
+            ssrRender(_ctx, push, parent) {
+              push(`<div>parent`)
+              push(
+                renderComponent(OptimizedChild, { msg: 'opt' }, null, parent)
+              )
+              push(renderComponent(VNodeChild, { msg: 'vnode' }, null, parent))
+              push(`</div>`)
+            }
+          })
+        )
+      ).toBe(`<div>parent<div>opt!</div><div>vnode!</div></div>`)
+    })
   })
 
-  describe('components', () => {
-    test('nested components', () => {})
+  describe('scopeId', () => {
+    // TODO
+  })
 
-    test('nested components with optimized slots', () => {})
+  describe('vnode', () => {
+    test('text children', () => {})
 
-    test('mixing optimized / vnode components', () => {})
+    test('array children', () => {})
 
-    test('nested components with vnode slots', () => {})
+    test('void elements', () => {})
 
-    test('async components', () => {})
+    test('innerHTML', () => {})
 
-    test('parallel async components', () => {})
+    test('textContent', () => {})
+
+    test('textarea value', () => {})
   })
 })
index a88c62c353a9a5d85697aed2222f105cff23d954..1cc3706caca4879d387e5db42e2cf9a56b90c567 100644 (file)
@@ -125,7 +125,7 @@ function renderComponentSubTree(
   } else {
     if (comp.ssrRender) {
       // optimized
-      comp.ssrRender(push, instance.proxy)
+      comp.ssrRender(instance.proxy, push, instance)
     } else if (comp.render) {
       renderVNode(push, renderComponentRoot(instance), instance)
     } else {
@@ -260,8 +260,8 @@ export function renderSlot(
 ) {
   // template-compiled slots are always rendered as fragments
   push(`<!---->`)
-  if (slotFn.length > 2) {
-    // only ssr-optimized slot fns accept 3 arguments
+  if (slotFn.length > 1) {
+    // only ssr-optimized slot fns accept more than 1 arguments
     slotFn(slotProps, push, parentComponent)
   } else {
     // normal slot
index 62c98dbdb1e7987bc272a185596cae7154fde49b..4a61ca375fc6935f183c6832ec14ab59330d4c0d 100644 (file)
@@ -20,7 +20,8 @@
     "types": ["jest", "puppeteer", "node"],
     "rootDir": ".",
     "paths": {
-      "@vue/*": ["packages/*/src"]
+      "@vue/*": ["packages/*/src"],
+      "vue": ["packages/vue/src"]
     }
   },
   "include": [