]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(ssr): test rendering vnode elements
authorEvan You <yyx990803@gmail.com>
Wed, 29 Jan 2020 22:36:06 +0000 (17:36 -0500)
committerEvan You <yyx990803@gmail.com>
Wed, 29 Jan 2020 22:36:06 +0000 (17:36 -0500)
packages/runtime-core/src/index.ts
packages/server-renderer/__tests__/renderProps.spec.ts
packages/server-renderer/__tests__/renderToString.spec.ts
packages/server-renderer/src/renderProps.ts
packages/server-renderer/src/renderToString.ts

index 12267adb0a3c1f7474a3c3f400c6fb2ad65982ab..bbd4dc98c07e41d2e6d1e884d51d6ddba1435e22 100644 (file)
@@ -104,13 +104,14 @@ export { registerRuntimeCompiler } from './component'
 // SSR -------------------------------------------------------------------------
 import { createComponentInstance, setupComponent } from './component'
 import { renderComponentRoot } from './componentRenderUtils'
-import { normalizeVNode } from './vnode'
+import { isVNode, normalizeVNode } from './vnode'
 
 // SSR utils are only exposed in cjs builds.
 const _ssrUtils = {
   createComponentInstance,
   setupComponent,
   renderComponentRoot,
+  isVNode,
   normalizeVNode
 }
 
index f5a6f60da28b07ab061347c4cafd152d94093df9..25efd80f7594e4efe987226af7419a3b937b07fd 100644 (file)
@@ -55,6 +55,17 @@ describe('ssr: renderProps', () => {
       })
     ).toBe(` readonly for="foobar"`)
   })
+
+  test('preserve name on custom element', () => {
+    expect(
+      renderProps(
+        {
+          fooBar: 'ok'
+        },
+        'my-el'
+      )
+    ).toBe(` fooBar="ok"`)
+  })
 })
 
 describe('ssr: renderClass', () => {
index ffab23a078443fa4a5d440a2752e1a115d5e0292..a41c4eedc6e9a93ff83e0caf86f2d9c6a70379c7 100644 (file)
@@ -1,5 +1,5 @@
-import { createApp, h } from 'vue'
-import { renderToString, renderComponent, renderSlot } from '../src'
+import { createApp, h, createCommentVNode } from 'vue'
+import { renderToString, renderComponent, renderSlot, escapeHtml } from '../src'
 
 describe('ssr: renderToString', () => {
   describe('components', () => {
@@ -251,21 +251,82 @@ describe('ssr: renderToString', () => {
     })
   })
 
-  describe('scopeId', () => {
-    // TODO
-  })
+  describe('vnode element', () => {
+    test('props', async () => {
+      expect(
+        await renderToString(
+          h('div', { id: 'foo&', class: ['bar', 'baz'] }, 'hello')
+        )
+      ).toBe(`<div id="foo&amp;" class="bar baz">hello</div>`)
+    })
 
-  describe('vnode', () => {
-    test('text children', () => {})
+    test('text children', async () => {
+      expect(await renderToString(h('div', 'hello'))).toBe(`<div>hello</div>`)
+    })
 
-    test('array children', () => {})
+    test('array children', async () => {
+      expect(
+        await renderToString(
+          h('div', [
+            'foo',
+            h('span', 'bar'),
+            [h('span', 'baz')],
+            createCommentVNode('qux')
+          ])
+        )
+      ).toBe(
+        `<div>foo<span>bar</span><!----><span>baz</span><!----><!--qux--></div>`
+      )
+    })
+
+    test('void elements', async () => {
+      expect(await renderToString(h('input'))).toBe(`<input>`)
+    })
 
-    test('void elements', () => {})
+    test('innerHTML', async () => {
+      expect(
+        await renderToString(
+          h(
+            'div',
+            {
+              innerHTML: `<span>hello</span>`
+            },
+            'ignored'
+          )
+        )
+      ).toBe(`<div><span>hello</span></div>`)
+    })
 
-    test('innerHTML', () => {})
+    test('textContent', async () => {
+      expect(
+        await renderToString(
+          h(
+            'div',
+            {
+              textContent: `<span>hello</span>`
+            },
+            'ignored'
+          )
+        )
+      ).toBe(`<div>${escapeHtml(`<span>hello</span>`)}</div>`)
+    })
 
-    test('textContent', () => {})
+    test('textarea value', async () => {
+      expect(
+        await renderToString(
+          h(
+            'textarea',
+            {
+              value: `<span>hello</span>`
+            },
+            'ignored'
+          )
+        )
+      ).toBe(`<textarea>${escapeHtml(`<span>hello</span>`)}</textarea>`)
+    })
+  })
 
-    test('textarea value', () => {})
+  describe('scopeId', () => {
+    // TODO
   })
 })
index 112055d89780abd965736cf76f1089987ce5e7d4..53536f70132b77c66c35f0ded5049da90bfd6879 100644 (file)
@@ -8,16 +8,23 @@ import {
   isNoUnitNumericStyleProp,
   isOn,
   isSSRSafeAttrName,
-  isBooleanAttr
+  isBooleanAttr,
+  makeMap
 } from '@vue/shared'
 
+const shouldIgnoreProp = makeMap(`key,ref,innerHTML,textContent`)
+
 export function renderProps(
   props: Record<string, unknown>,
-  isCustomElement: boolean = false
+  tag?: string
 ): string {
   let ret = ''
   for (const key in props) {
-    if (key === 'key' || key === 'ref' || isOn(key)) {
+    if (
+      shouldIgnoreProp(key) ||
+      isOn(key) ||
+      (tag === 'textarea' && key === 'value')
+    ) {
       continue
     }
     const value = props[key]
@@ -26,9 +33,10 @@ export function renderProps(
     } else if (key === 'style') {
       ret += ` style="${renderStyle(value)}"`
     } else if (value != null) {
-      const attrKey = isCustomElement
-        ? key
-        : propsToAttrMap[key] || key.toLowerCase()
+      const attrKey =
+        tag && tag.indexOf('-') > 0
+          ? key // preserve raw name on custom elements
+          : propsToAttrMap[key] || key.toLowerCase()
       if (isBooleanAttr(attrKey)) {
         if (value !== false) {
           ret += ` ${attrKey}`
index 1cc3706caca4879d387e5db42e2cf9a56b90c567..7cd7c25ce711d82322f31c76ebba49f887cbee77 100644 (file)
@@ -12,7 +12,8 @@ import {
   Portal,
   ShapeFlags,
   ssrUtils,
-  Slot
+  Slot,
+  createApp
 } from 'vue'
 import {
   isString,
@@ -25,6 +26,7 @@ import { renderProps } from './renderProps'
 import { escapeHtml } from './ssrUtils'
 
 const {
+  isVNode,
   createComponentInstance,
   setupComponent,
   renderComponentRoot,
@@ -81,7 +83,15 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string {
   return ret
 }
 
-export async function renderToString(app: App): Promise<string> {
+export async function renderToString(input: App | VNode): Promise<string> {
+  if (isVNode(input)) {
+    return renderAppToString(createApp({ render: () => input }))
+  } else {
+    return renderAppToString(input)
+  }
+}
+
+async function renderAppToString(app: App): Promise<string> {
   const resolvedBuffer = await renderComponent(app._component, app._props, null)
   return unrollBuffer(resolvedBuffer)
 }
@@ -143,7 +153,7 @@ function renderComponentSubTree(
   return hasAsync() ? Promise.all(buffer) : (buffer as ResolvedSSRBuffer)
 }
 
-export function renderVNode(
+function renderVNode(
   push: PushFn,
   vnode: VNode,
   parentComponent: ComponentInternalInstance | null = null
@@ -203,7 +213,7 @@ function renderElement(
   // TODO directives
 
   if (props !== null) {
-    openTag += renderProps(props, tag.indexOf(`-`) > 0)
+    openTag += renderProps(props, tag)
   }
 
   if (scopeId !== null) {