Sfoglia il codice sorgente

tests for scoped slots

Evan You 9 anni fa
parent
commit
7f36f99f6c

+ 1 - 0
flow/compiler.js

@@ -87,6 +87,7 @@ declare type ASTElement = {
   transitionMode?: string | null;
   slotName?: ?string;
   slotTarget?: ?string;
+  slotScope?: ?string;
   scopedSlots?: { [name: string]: ASTElement };
 
   ref?: string;

+ 11 - 10
src/compiler/codegen/index.js

@@ -158,10 +158,6 @@ function genData (el: ASTElement): string {
   if (el.component) {
     data += `tag:"${el.tag}",`
   }
-  // slot target
-  if (el.slotTarget) {
-    data += `slot:${el.slotTarget},`
-  }
   // module data generation functions
   for (let i = 0; i < dataGenFns.length; i++) {
     data += dataGenFns[i](el)
@@ -181,14 +177,21 @@ function genData (el: ASTElement): string {
   if (el.nativeEvents) {
     data += `${genHandlers(el.nativeEvents, true)},`
   }
-  // inline-template
-  if (el.inlineTemplate) {
-    data += `${genInlineTemplate(el)},`
+  // slot target
+  if (el.slotTarget) {
+    data += `slot:${el.slotTarget},`
   }
   // scoped slots
   if (el.scopedSlots) {
     data += `${genScopedSlots(el.scopedSlots)},`
   }
+  // inline-template
+  if (el.inlineTemplate) {
+    const inlineTemplate = genInlineTemplate(el)
+    if (inlineTemplate) {
+      data += `${inlineTemplate},`
+    }
+  }
   data = data.replace(/,$/, '') + '}'
   // v-bind data wrap
   if (el.wrapData) {
@@ -228,7 +231,7 @@ function genDirectives (el: ASTElement): string | void {
   }
 }
 
-function genInlineTemplate (el) {
+function genInlineTemplate (el: ASTElement): ?string {
   const ast = el.children[0]
   if (process.env.NODE_ENV !== 'production' && (
     el.children.length > 1 || ast.type !== 1
@@ -242,8 +245,6 @@ function genInlineTemplate (el) {
     }},staticRenderFns:[${
       inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',')
     }]}`
-  } else {
-    return ''
   }
 }
 

+ 10 - 1
src/compiler/parser/index.js

@@ -169,8 +169,9 @@ export function parse (
       if (currentParent && !element.forbidden) {
         if (element.else) { // else block
           processElse(element, currentParent)
-        } else if (element.slotTarget && element.attrsMap.scope) { // scoped slot
+        } else if (element.slotTarget && element.slotScope) { // scoped slot
           (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[element.slotTarget] = element
+          currentParent.plain = false
         } else {
           currentParent.children.push(element)
           element.parent = currentParent
@@ -344,10 +345,18 @@ function processOnce (el) {
 function processSlot (el) {
   if (el.tag === 'slot') {
     el.slotName = getBindingAttr(el, 'name')
+    if (process.env.NODE_ENV !== 'production' && el.key) {
+      warn(
+        `\`key\` does not work on <slot> because slots are abstract outlets ` +
+        `and can possibly expand into multiple elements. ` +
+        `Use the key on a wrapping element instead.`
+      )
+    }
   } else {
     const slotTarget = getBindingAttr(el, 'slot')
     if (slotTarget) {
       el.slotTarget = slotTarget
+      el.slotScope = getAndRemoveAttr(el, 'scope')
     }
   }
 }

+ 7 - 6
src/core/instance/render.js

@@ -191,12 +191,13 @@ export function renderMixin (Vue: Class<Component>) {
     fallback: ?Array<VNode>,
     props: ?Object
   ): ?Array<VNode> {
-    if (props) { // scoped slot
-      const scopedSlotFn = this.$scopedSlots[name]
-      return scopedSlotFn
-        ? scopedSlotFn(props) || fallback
-        : fallback
-    } else { // static slot
+    if (process.env.NODE_ENV !== 'production' && name === 'default' && props) {
+      warn(`Scoped slots must be named`, this)
+    }
+    const scopedSlotFn = this.$scopedSlots && this.$scopedSlots[name]
+    if (scopedSlotFn) { // scoped slot
+      return scopedSlotFn(props || {}) || fallback
+    } else {
       const slotNodes = this.$slots[name]
       // warn duplicate slot usage
       if (slotNodes && process.env.NODE_ENV !== 'production') {

+ 252 - 0
test/unit/features/component/component-scoped-slot.spec.js

@@ -0,0 +1,252 @@
+import Vue from 'vue'
+
+describe('Component scoped slot', () => {
+  it('normal element slot', done => {
+    const vm = new Vue({
+      template: `
+        <test ref="test">
+          <span slot="item" scope="props">{{ props.text }}</span>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return { msg: 'hello' }
+          },
+          template: `
+            <div>
+              <slot name="item" :text="msg"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+
+    expect(vm.$el.innerHTML).toBe('<span>hello</span>')
+    vm.$refs.test.msg = 'world'
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>world</span>')
+    }).then(done)
+  })
+
+  it('template slot', done => {
+    const vm = new Vue({
+      template: `
+        <test ref="test">
+          <template slot="item" scope="props">
+            <span>{{ props.foo }}</span><span>{{ props.bar }}</span>
+          </template>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return { foo: 'FOO', bar: 'BAR' }
+          },
+          template: `
+            <div>
+              <slot name="item" :foo="foo" :bar="bar"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+
+    expect(vm.$el.innerHTML).toBe('<span>FOO</span><span>BAR</span>')
+    vm.$refs.test.foo = 'BAZ'
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>BAZ</span><span>BAR</span>')
+    }).then(done)
+  })
+
+  it('fallback content', () => {
+    const vm = new Vue({
+      template: `<test></test>`,
+      components: {
+        test: {
+          data () {
+            return { msg: 'hello' }
+          },
+          template: `
+            <div>
+              <slot name="item" :text="msg">
+                <span>{{ msg }} fallback</span>
+              </slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<span>hello fallback</span>')
+  })
+
+  it('slot with v-for', done => {
+    const vm = new Vue({
+      template: `
+        <test ref="test">
+          <template slot="item" scope="props">
+            <span>{{ props.text }}</span>
+          </template>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return {
+              items: ['foo', 'bar', 'baz']
+            }
+          },
+          template: `
+            <div>
+              <slot v-for="item in items" name="item" :text="item"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+
+    function assertOutput () {
+      expect(vm.$el.innerHTML).toBe(vm.$refs.test.items.map(item => {
+        return `<span>${item}</span>`
+      }).join(''))
+    }
+
+    assertOutput()
+    vm.$refs.test.items.reverse()
+    waitForUpdate(assertOutput).then(() => {
+      vm.$refs.test.items.push('qux')
+    }).then(assertOutput).then(done)
+  })
+
+  it('slot inside v-for', done => {
+    const vm = new Vue({
+      template: `
+        <test ref="test">
+          <template slot="item" scope="props">
+            <span>{{ props.text }}</span>
+          </template>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return {
+              items: ['foo', 'bar', 'baz']
+            }
+          },
+          template: `
+            <ul>
+              <li v-for="item in items">
+                <slot name="item" :text="item"></slot>
+              </li>
+            </ul>
+          `
+        }
+      }
+    }).$mount()
+
+    function assertOutput () {
+      expect(vm.$el.innerHTML).toBe(vm.$refs.test.items.map(item => {
+        return `<li><span>${item}</span></li>`
+      }).join(''))
+    }
+
+    assertOutput()
+    vm.$refs.test.items.reverse()
+    waitForUpdate(assertOutput).then(() => {
+      vm.$refs.test.items.push('qux')
+    }).then(assertOutput).then(done)
+  })
+
+  it('scoped slot without scope alias', () => {
+    const vm = new Vue({
+      template: `
+        <test ref="test">
+          <span slot="item">I am static</span>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return { msg: 'hello' }
+          },
+          template: `
+            <div>
+              <slot name="item" :text="msg"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<span>I am static</span>')
+  })
+
+  it('non-scoped slot with scope alias', () => {
+    const vm = new Vue({
+      template: `
+        <test ref="test">
+          <span slot="item" scope="props">{{ props.text || 'meh' }}</span>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return { msg: 'hello' }
+          },
+          template: `
+            <div>
+              <slot name="item"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<span>meh</span>')
+  })
+
+  it('warn un-named scoped slot', () => {
+    new Vue({
+      template: `<test><span scope="lol"></span></test>`,
+      components: {
+        test: {
+          data () {
+            return { msg: 'hello' }
+          },
+          template: `
+            <div>
+              <slot :msg="msg"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+    expect('Scoped slots must be named').toHaveBeenWarned()
+  })
+
+  it('warn key on slot', () => {
+    new Vue({
+      template: `
+        <test ref="test">
+          <template slot="item" scope="props">
+            <span>{{ props.text }}</span>
+          </template>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return {
+              items: ['foo', 'bar', 'baz']
+            }
+          },
+          template: `
+            <div>
+              <slot v-for="item in items" name="item" :text="item" :key="item"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+    expect(`\`key\` does not work on <slot>`).toHaveBeenWarned()
+  })
+})